boolean serializing to string error - serialization

I have a property like this:
#CatalogExportField(columnName = "K", headerName = "catalog name")
private Boolean mpAvailable;
I need to get this as string while parsing in other class
private CatalogExportDto convert(Variant variant, boolean willHaveProductTypeFields) {
CatalogExportDto dto = new CatalogExportDto()
.setMpAvailable(variant.isMpAvailable())
But here it is boolean.
but could not find any proper example.
this is also
catalog export field.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CatalogExportField {
String color() default "#56aaff";
String columnName() default "";
String headerName() default "";
String displayName() default "";
}
After searching, i did some and got error:
Problem deserializing property 'MpAvailable' (expected type: [simple type, class java.lang.Boolean]; actual type: java.lang.String), problem: argument type mismatch at [Source: java.io.FileInputStream#1f40904; line: 1, column: 720] (through reference chain: java.util.ArrayList[0]->domain.util.CatalogExportDto["MpAvailable"])
for
this is in catalogexport dto
#JsonDeserialize(using = BooleanDeserializer.class)
#JsonProperty("MpAvailable")
#CatalogExportField(columnName = "K", headerName = "catalog.export.mp_available")
private Boolean mpAvailable;
this is deserializer
public class BooleanDeserializer extends JsonDeserializer<String> {
protected static final String NO = "no";
protected static final String YES = "yes";
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken currentToken = jp.getCurrentToken();
if (currentToken.equals(JsonToken.VALUE_FALSE)) {
return NO;
}
return YES;
}
}
I changed to this now
public class YesNoBooleanSerializer extends JsonSerializer {
protected static final String NO = "no";
protected static final String YES = "yes";
#Override
public void serialize(Boolean b, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(b ? NO : YES);
}
}
}
}
field
#JsonSerialize(using = YesNoBooleanSerializer.class)
#CatalogExportField(columnName = "K", headerName = "catalog.export.mp_available")
private Boolean mpAvailable;
i got error
Can not deserialize value of type java.lang.Boolean from String "no": only "true" or "false" recognized at [Source: java.io.FileInputStream#1d1ec976; line: 1, column: 534] (through reference chain: java.util.ArrayList[0]->CatalogExportDto["mpAvailable"])

A couple unordered thougts:
when you write JsonDeserializer<String>, probably you say that it is going to be your class which deserializes String-s from JSON. Actually you want to deserialize to either some weird own type, or to Boolean, but not to String, that is sure
if you serialize by writing "yes" or "no", you would deserialize by comparing to "yes" or "no". But you compare to JsonToken.VALUE_FALSE, which is most likely "false" or something
however your deserialize method probably did not run at all: as it presumably gets some "yes"/"no" text, and compares it to "false", the comparison would fail, so the method would return YES ("yes"), while the error message complains about getting a "no"
I know nothing about Spring or Boot, but writing
public class BooleanDeserializer extends JsonDeserializer<Boolean> {
protected static final String NO = "no";
protected static final String YES = "yes";
#Override
public Boolean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken currentToken = jp.getCurrentToken();
return currentToken.equals(YES);
}
}
might be a better start. However I really do not know if this would be invoked at all, as JSON already has its own Boolean deserializer for sure.
According to Right way to write JSON deserializer in Spring or extend it you may rather want to use this deserialization stuff for your own classes.

Related

Access JAX-RS resource annotations from a JsonbSerializer

I have an application running on Payara 4 using a custom GSON JSON adapter. I would like to migrate to Payara 5 (5.191) and start using JSON-B. In our current application we can control the JSON output using annotations on a resource.
For example using #Summarize:
#GET
#Path("summary/{encryptedId}")
#Produces(MediaType.APPLICATION_JSON)
#Summarize
public Address findSummarized(#PathParam("encryptedId") String encryptedId) {
return super.find(encryptedId);
}
it will cause a different GSON configuration to be used in our #Provider:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class GsonProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
public GsonProvider() {
gson = getGson(EntityAdapter.class);
gsonSummary = getGson(EntitySummaryAdapter.class);
}
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? gsonSummary : gson).toJson(object));
printWriter.flush();
}
}
}
I want to do something similar in the new JSON-B setup. I annotated our entities with #JsonbTypeSerializer(MySerializer.class), so I would like to be able to detect from within the serializer what it should do: either create a full serialized JSON object, or a summary.
What I hoped to do is set a property in the JsonbConfig, like so:
JsonbConfig config = new JsonbConfig()
.setProperty("com.myCompany.jsonb.summarize", true);
and read it in the serializer using #Context (just guessing that this might work here), like so:
#Context
private JsonbConfiguration config;
.. but that's not. Is there any way to access JAX-RS resource annotations from a JsonbSerializer?
You could accomplish a similar goal using two separate Jsonb instances in your JAX-RS provider class like so:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class JsonbProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
private static final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntityAdapter()));
private static final Jsonb jsonbSummary = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntitySummaryAdapter()));
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? jsonbSummary : jsonb).toJson(object));
printWriter.flush();
}
}
}
In the end I opted to create summaries from within my entities and drop the annotation on my REST resources. It was a bit of work, but I think it has been worth it.
I created a Summarizable interface and added a default method there to create a simple map summary of any entity, based on a extended version of the PropertyVisibilityStrategy we created for the full version of the entities.
public interface Summarizable {
public default Map<String, Object> toSummary() {
SummaryPropertyVisibilityStrategy summaryStrategy = new SummaryPropertyVisibilityStrategy();
Map<String, Object> summary = new LinkedHashMap<>();
ReflectionUtils.getFields(this.getClass())
.stream()
.filter(summaryStrategy::isVisible)
.map(f -> new AbstractMap.SimpleEntry<>(f.getName(), summarize(f)))
.filter(e -> e.getValue() != null)
.forEach(e -> summary.put(e.getKey(), e.getValue()));
return summary;
}
public default Object summarize(final Field field) {
Object value = ReflectionUtils.getValueJsonb(this, field);
return value != null && Stream.of(ManyToOne.class, OneToOne.class).anyMatch(field::isAnnotationPresent)
? value.toString()
: value;
}
}
public static Object getValueJsonb(final Object object, final Field field) {
field.setAccessible(true);
JsonbTypeAdapter adapterAnnotation = field.getAnnotation(JsonbTypeAdapter.class);
try {
Object value = field.get(object);
return adapterAnnotation == null
? value
: adapterAnnotation.value().newInstance().adaptToJson(value);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

hide Jackson fields based on costume dynamic criteria for JaxRS Respose

Idea is simple. I have a object and I would like to hide some fields based on the some specific roles.
I have roles in the system "dog", "cat" etc.
class Food{
String name;
#HideInfoForTheRoles({"dog", "cat"})
String age;
}
So I think to create something like that:
public String hideForRole(T object, String role){
// return new json
}
Or maybe I can override some denationalization method to force Jackson to hide field based on my annotation?
You could use #JsonView. That's probably the easiest solution, as #JsonView works out-of-the-box with JAX-RS.
Alternativerly, it could be achieved with a BeanPropertyFilter, similar to another solution I put together a while ago.
Start defining your annotation:
#Documented
#Retention(RUNTIME)
#Target({FIELD})
public #interface HiddenForRoles {
String[] value();
}
Then define your BeanPropertyFilter, which can extend SimpleBeanPropertyFilter:
public class HiddenForRolesPropertyFilter extends SimpleBeanPropertyFilter {
private String allowedRole;
public HiddenForRolesPropertyFilter(String allowedRole) {
this.allowedRole = allowedRole;
}
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider,
PropertyWriter writer) throws Exception {
HiddenForRoles hiddenForRoles = writer.getAnnotation(HiddenForRoles.class);
if (hiddenForRoles != null) {
if (Arrays.asList(hiddenForRoles.value()).contains(allowedRole)) {
writer.serializeAsOmittedField(pojo, jgen, provider);
return;
}
}
// If no annotation is provided, the property will be serialized
writer.serializeAsField(pojo, jgen, provider);
}
}
Place the #HiddenForRoles annotation in your fields, according to your needs and ensure the class is annotated with #JsonFilter:
#Data
#JsonFilter("hiddenForRolesPropertyFilter")
public class Foo {
private String bar;
#HiddenForRoles({"cat"})
private String biz;
}
Finally, register the filter in a ContextResolver for ObjectMapper:
String currentUserRole = // Get role from the current user
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("hiddenForRolesPropertyFilter",
new HiddenForRolesPropertyFilter(currentUserRole));
ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);
If you want to make your filter "global", that is, to be applied to all beans, you can create a mix-in class and annotate it with #JsonFilter:
#JsonFilter("hiddenForRolesPropertyFilter")
public class HiddenForRolesPropertyFilterMixIn {
}
Then bind the mix-in class to Object:
mapper.addMixIn(Object.class, HiddenForRolesPropertyFilterMixIn.class);
Create annotation that supports on FIELD and METHOD
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface HideFor{
String[] roles() default{};
}
and logic that supports annotation for both field and methods
public class AccessRestrictionFilter extends SimpleBeanPropertyFilter {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if(writer.getAnnotation(HideFor.class)!=null && isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
logger.debug("Found restriction on the getter method of the field: " + pojo + " Restriction For" + Arrays.toString(writer.getAnnotation(HideFor.class).roles()) );
return;
}
Field[] fields = jgen.getCurrentValue().getClass().getDeclaredFields();
Optional<Field> field = Arrays.stream(fields)
.filter(f-> f.getName().equalsIgnoreCase(writer.getName())).findAny();
if(field.isPresent() && field.get().getAnnotation(HideFor.class)!=null){
if(isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
System.out.println("Found restriction on the field " + field.get().getName() + " Restriction For " + Arrays.toString(writer.getAnnotation(HideFor.class).roles()));
return;
}
}
writer.serializeAsField(pojo, jgen, provider);
}
private boolean isHidable(List<String> rolesToHide){ // imlement the logic // }
}
Usage:
FilterProvider filterProvider = new SimpleFilterProvider().addFilter("AccessRestrictionFilter", new AccessRestrictionFilter());
new ObjectMapper().writer(filterProvider ).writeValueAsString(myObjToFilter);
I use Jersey/Spring and my configuration looks like this:
#Provider
#Produces({MediaType.APPLICATION_JSON})
public class JacksonJsonProvider extends JacksonJaxbJsonProvider {
public JacksonJsonProvider(AccessRestrictionFilter filter) {
ObjectMapper objectMapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setFilterProvider(new SimpleFilterProvider().addFilter("AccessRestriction", filter));
setMapper(objectMapper);
}
}
And Filter:
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Bean("accessRestrictionFilter")
public AccessRestrictionFilter accessRestrictionFilter(){
return new AccessRestrictionFilter();
}
Note: in the filter I use the Security Context, because of this scope of the filter is Session (Not to share the state but create new object for each user)
and that's my POJO:
#JsonFilter("AccessRestrictionFilter")
public class MyClass {
#HideFor(roles = {"ROLE_USER", "ROLE_EDITOR"})
private int val;

Using [FromQuery] to parse enum values

I'm using the [FromQuery] attribute to parse a Get requests arguments into a complex object. For example:
[HttpGet("some-get-request")]
public IActionResult DoSomething([FromQuery] SomeArguments someArgs)
{
}
One of the properties of the SomeArguments object is an enum.
public enum SomeEnum { EnumValue01, EnumValue02 }
public class SomeArguments
{
[FromQuery(Name = "enum_arg")]
public SomeEnum EnumArgument { get; set; }
}
And I call the endpoint with something like:
http://localhost:1234/api/some-controller/some-get-request?enum_arg=EnumValue01
And this all works great. However, I want to be able to use a different enum value in the URL than in my C# enum value. For example, I want to call using a URL such as
http://localhost:1234/api/some-controller/some-get-request?enum_arg=eval01
How can I do this?
I thought I could use the [FromQuery] attribute, like I can with properties, but that doesnt seem to be possible:
'FromQuery' is not valid on this declaration type. It is only valid on 'property, indexer, parameter'
You can use EnumMemberAttribute in conjunction with StringEnumConverter to achieve your goal. Define SomeEnum as following
[JsonConverter(typeof(StringEnumConverter))]
public enum SomeEnum
{
[EnumMember(Value = "eval01")]
EnumValue01,
[EnumMember(Value = "eval02")]
EnumValue02
}
At this point it will work as you need only when Newtonsoft json serializer is used. For example, when controller expects POST request and parameter is marked as [FromBody]. In your case it won't work yet because during binding of [FromQuery] parameter json serializer is not used. To solve this one create custom model binder
public class JsonModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string rawData = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
rawData = JsonConvert.SerializeObject(rawData); //turns value to valid json
try
{
SomeEnum result = JsonConvert.DeserializeObject<SomeEnum>(rawData); //manually deserializing value
bindingContext.Result = ModelBindingResult.Success(result);
}
catch (JsonSerializationException ex)
{
//do nothing since "failed" result is set by default
}
return Task.CompletedTask;
}
}
Update SomeEnum definition to use JsonModelBinder
[JsonConverter(typeof(StringEnumConverter))]
[ModelBinder(typeof(JsonModelBinder))]
public enum SomeEnum

Immutables wrapper as string in Jackson

How can I jackson serialize a wrapper type to and from a string?
I merged the following from two different examples their website. But the HostName type is serialized/deserialized as
{ "name" : "my.host.name.com" }
when I want it to be simply the string
"my.host.name.com"
Note that I have a lot of XName types, hence the use of the Immutables wrapper. So I would prefer a solution that keeps the amount of boiler plate down.
#Value.Immutable #AbstractName.Wrapper
public abstract class _HostName extends AbstractName { }
...
public abstract class AbstractName {
#JsonSerialize
#JsonDeserialize
#Value.Style(
// Detect names starting with underscore
typeAbstract = "_*",
// Generate without any suffix, just raw detected name
typeImmutable = "*",
// Make generated public, leave underscored as package private
visibility = Value.Style.ImplementationVisibility.PUBLIC,
// Seems unnecessary to have builder or superfluous copy method
defaults = #Value.Immutable(builder = false, copy = false))
public #interface Wrapper {}
#Value.Parameter
public abstract String name();
#Override
public String toString() { return name(); }
}
I've got this to work like below. There's an extra annotation on my name types. It's not my favorite, but it works.
#JsonDeserialize(as=HostName.class)
#Value.Immutable #AbstractName.Wrapper
public abstract class _HostName extends AbstractName { }
...
public abstract class AbstractName {
#Value.Style(
// Detect names starting with underscore
typeAbstract = "_*",
// Generate without any suffix, just raw detected name
typeImmutable = "*",
// Make generated public, leave underscored as package private
visibility = Value.Style.ImplementationVisibility.PUBLIC,
// Seems unnecessary to have builder or superfluous copy method
defaults = #Value.Immutable(builder = false, copy = false))
public #interface Wrapper {}
#JsonValue
#Value.Parameter
public abstract String name();
#Override
public String toString() { return name(); }
}
Here's a little program to run it:
public static void main(String... args) throws IOException {
ObjectMapper json = new ObjectMapper();
String text = json.writeValueAsString(HostName.of("my.host.name.com"));
System.out.println(text);
HostName hostName = json.readValue(text, HostName.class);
System.out.println(hostName);
}

Morphia Interface for List of enum does not work (unmarshalling)

I have the following interface
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "className")
public interface InfoChartInformation {
public String name();
}
And the following implementation (enum):
public class InfoChartSummary {
public static enum Immobilien implements InfoChartInformation {
CITY, CONSTRUCTION_DATE;
}
public static enum Cars implements InfoChartInformation {
POWER, MILEAGE;
}
}
Then I use all of It in the following entity:
#Entity(noClassnameStored = true)
#Converters(InfoChartInformationMorphiaConverter.class)
public class TestEntity{
#Id
public ObjectId id;
#Embedded
public List<InfoChartInformation> order;
}
Jackson, in order to detect the type on the unmarshalling time, will add to every enum on the list the className.
I thought morphia would do the same, but there's no field className in the List of enum and the unmarshalling cannot be done correctly: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: java.lang.String cannot be cast to com.mongodb
.DBObject
I guess the correct behavior should be to save all the enum route (package+name), not only the enum name. At least in that way the unmarshalling could be performed. There's a way morphia supports that by default or I need to create my own converter (similar to this) ?
I tried creating a Custom Converter:
public class InfoChartInformationMorphiaConverter extends TypeConverter{
public InfoChartInformationMorphiaConverter() {
super(InfoChartInformation.class);
}
#Override
public Object decode(Class targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
if (fromDBObject == null) {
return null;
}
String clazz = fromDBObject.toString().substring(0, fromDBObject.toString().lastIndexOf("."));
String value = fromDBObject.toString().substring(fromDBObject.toString().lastIndexOf(".") + 1);
try {
return Enum.valueOf((Class)Class.forName(clazz), value);
} catch (ClassNotFoundException e) {
return null;
}
}
#Override
public Object encode(final Object value, final MappedField optionalExtraInfo) {
return value.getClass().getName() + "." + ((InfoChartInformation) value).name();
}
}
Then, I added the converter information to morphia morphia.getMapper().getConverters().addConverter(new InfoChartInformationMorphiaConverter());.
However, when serializing (or marshalling) the object to save it into the database, the custom converter is ignored and the Enum is saved using the default Morphia converter (only the enum name).
If I use in the TestEntity class only an attribute InfoChartInformation; instead of the List<>InfoChartInformation>, my customer converter will work. However I need support for List
Use:
public class InfoChartInformationMorphiaConverter extends TypeConverter implements SimpleValueConverter
It is a marker interface required to make your Convertor work.