Currently I am using the YAMLFactory to configure the ObjectMapper to serialise and deserialise Pojos <=> YAML, however it writes null values in serialisation despite attempting the usual tricks in Jackson.
Annotating with #JsonInclude(JsonInclude.Include.NON_NULL) on the class level or the field level has no affect. I have also tried annotating classes with #JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) with no affect either. How do you achieve this when using the YAMLFactory?
I have found a similar question but the use case does not appear to be the same. To be clear I am trying to omit the field altogether.
Edit: Here is an example (I am also using Lombok)
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class QueueProperties extends Properties {
#JsonProperty("QueueName")
private String queueName;
#JsonProperty("RedrivePolicy")
private RedrivePolicy redrivePolicy;
public Optional<RedrivePolicy> getRedrivePolicy() {
return Optional.ofNullable(redrivePolicy);
}
}
when serialized:
Properties:
QueueName: 471416d1-3643-4d5a-a033-65f22757dcaf-chargeback-paypal-ingestion-ingest_dispute
RedrivePolicy: null
ObjectMapper configuration:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.MINIMIZE_QUOTES));
mapper.registerModule(new Jdk8Module());
getRedrivePolicy getter method always returns not-null object, even so, Optional could reference to null. In this case, you should skip empty objects. Optional with null is considered as empty and we can use JsonInclude.Include.NON_EMPTY for it. You can keep #JsonInclude(JsonInclude.Include.NON_NULL) on class level and add NON_EMPTY only for given getter:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Optional<Object> getRedrivePolicy() {
return Optional.ofNullable(redrivePolicy);
}
Related
I'm trying to deserialize an array of MyObject (which uses the builder pattern via Lombok with #Jacksonized) from a csv String containing a non-standard representation of a map as one of the columns.
MyObject:
#JsonPropertyOrder({
"strField",
"mapField",
})
#Getter
#Jacksonized
#Builder(toBuilder = true)
#EqualsAndHashCode
#ToString
public class MyObject {
private final String strField;
#Builder.Default
private final Map<String, Float> mapField = new HashMap<>();
}
Example csv with non-standard mapField representation:
strField,mapField
abc,"key1=2.0;key2=3.0"
I'm using a mixin to try to achieve this without rewriting the entire object:
#JsonPropertyOrder({
"strField",
"mapField",
})
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
private Map<String, Float> mapField;
}
..which, as you can see above, points to a custom deserializer:
public class StringToMapDeserializer extends JsonDeserializer<Map<String, Float>> {
#Override
public Map<String, Float> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String csvFormattedMap = jsonParser.getText().trim();
return Arrays.stream(csvFormattedMap.split(";"))
.map(keyValue -> keyValue.split("="))
.collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> Float.parseFloat(keyValue[1])));
}
}
And, to wrap it all up, I'm configuring and using my CsvMapper like so:
CsvMapper csvMapper = new CsvMapper();
csvMapper.addMixIn(MyObject.class, MyObjectDeserializerMixin.class);
CsvSchema csvSchema = csvMapper
.schemaFor(MyObject.class)
.withHeader();
ObjectReader csvReader = csvMapper.readerFor(MyObject.class).with(csvSchema);
List<MyObject> myObjects = csvReader.<MyObject>readValues(theCsvString).readAll();
However, I'm getting the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of java.util.LinkedHashMap (although at least one
Creator exists): no String-argument constructor/factory method to
deserialize from String value ('key1=2.0;key2=3.0') at [Source:
(StringReader); line: 2, column: 53] (through reference chain:
myPackage.MyObject$MyObjectBuilder["mapField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromString(ValueInstantiator.java:258)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:357)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:193)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1261)
...
The stack trace appears to be trying to use a BuilderBasedDeserializer which tries to use a MapDeserializer.java for the map so it doesn't appear to be aware of my custom deserializer. I've used a very similar workflow with a custom serializer to write the same csv, so I'm confused as to why this doesn't work. What is the next step to troubleshooting this?
When using a builder to deserialize, Jackson only considers the annotations on the builder class, not those on the actual class to deserialize. Lombok's #Jacksonized helps you by automatically copying all relevant annotations to the builder class and its setter methods.
However, Lombok can only do that with annotations statically present on the class. Any dynamic annotation coming from a mixin cannot be copied, because Lombok doesn't know about them.
You could put #JsonDeserialize onto the mapField of the actual class, so that Lombok is able to copy it to the builder. But that obviously runs contrary to the purpose of mixins.
Luckily, there is a better way. You can also add the mixin to the builder class as follows:
csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);
Strictly speaking, you do not need the mixin on MyObject any more. But if you also serialize and there are annotations relevant to serialization in the mixin, you should add the mixin to both.
However, in your case, that's not sufficient, as you are using #Builder.Default. With that annotation, Lombok creates a field named mapField$value in the builder. Jackson will not match the field mapField (and its annotation) from your mixin to that field, as they are named differently.
You can work around that by defining and annotating the setter method in your mixin:
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
public abstract void mapField(Map<String, Float> mapField);
}
You can use the actual return type of the builder here, but void is also sufficient. As #JsonDeserialize is only for deserialization purposes, you can safely remove the mapField and its annotation from the mixin class.
Tested with Lombok 1.18.20 and Jackson 2.12.2.
I'm trying to use JsonNullable<String> to distinguish between the absence of a value and null. When I serialize my Java object, User, the JsonNullable<String> field is serialized as a JSON object with value
{"present":false}
I'm expecting it to print {} since I initialized the field with undefined.
Here's my class
public class User {
#JsonProperty("userId")
private JsonNullable<String> userId = JsonNullable.undefined();
//set and get
//tostring
}
and a small driver program where I actually set a value within the JsonNullable field.
User user = new User();
user.setUserId(JsonNullable.of("12345"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
String expectedData = objectMapper.writeValueAsString(user);
System.out.println(expectedData);
This prints
{"userId":{"present":true}}
But I expected
{"userId":"12345"}
In other words, I expected the value wrapped within the JsonNullable to be serialized into the JSON. Why are JsonNullable and Jackson behaving this way?
As the wiki instructs, you need to register the corresponding module (in addition to any others you have):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.registerModule(new JsonNullableModule());
Add the following configuration in Spring Boot:
#Configuration
public class JacksonConfiguration {
#Bean
public JsonNullableModule jsonNullableModule() {
return new JsonNullableModule();
}
}
Registering the JsonNullableModule is the correct solution. But I just want to point out that JsonNullable doesn't play well with openapi-generator. We generate client APIs from the backend controller annotations. The required, nullable annotations on #schema didn't work for us (on a Kotlin data class). We went back with using Optional<T>? and worked out perfectly with out any extra annotations needed.
I try to deserialize a JSON object that I receive in my API using the following code:
ObjectMapper mapper = new ObjectMapper();
ExampleDto ed = mapper.readValue(req.body(), ExampleDto.class);
My class uses Lombok to generate constructors, getters and setters, and looks like this:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ExampleDto {
private String name = "";
private List<String> values = new LinkedList<>();
}
Both properties should be optional, and use the default value specified in the class definition if they are not provided. However, if I now try to deserialize the JSON
{name: "Foo"}
the values field is null. From my understanding, and all example code I found, values should be an empty list.
Edit: Not a duplicate, as I'm using Lombok without Optionals
#AllArgsConstructor creates the following constructor
#ConstructorProperties({"name", "values"})
ExampleDto(String name, List<String> values) {
this.name = name;
this.values = values;
}
The constructor is annotated with #ConstructorProperties which means a property-based creator (argument-taking constructor or factory method) is available to instantiate values from JSON object so jackson-databind uses this constructor to instantiate an object from ExampleDto class.
When the following line executes
mapper.readValue("{\"name\": \"Foo\"}", ExampleDto.class);
because there's no value for values in the provided JSON, null is passed for the second argument when the constructor is invoked.
If you remove #AllArgsConstructor annotation jackson-databind would use setter methods to initialize the object and in this case values would not be null
When I'm parsing boolean value in JSON by using Jackson, I not only get my expected data, but also an extra key-value data. I want to deserialize the JSON into Java Beans and then serialize it into a String again after processing it. The extra data is in the finally result.Here is my JSON data:
{"is_charging": true}
But I get this after I parse it and then serialize it:
{"is_charging": true, "charging": true}
And here is my Java bean:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
However, if I don't use the #JsonProperty, it can not deserialize the "is_charging" and deserialize it as false by default.
How can I solve this? Thanks!
It is the lombok.Getter and lombok.Setter annotations that cause the issue.
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
objectMapper.writeValueAsString(new Data());
Works as expected.
The problem occurs when the #Getter and #Setter annotations are added.
I don't have experience with this lombok library but as far as I understand it creates getter and setter methods for you.
By configuring objectMapper you can disable auto detecting of getter and setter methods so only fields can be serialized and deserialized.
#Getter
#Setter
public class Data {
#JsonProperty("is_charging;")
public boolean isCharging;
}
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
Data data = objectMapper.readValue("{\"is_charging\": true}", Data.class);
System.out.print(objectMapper.writeValueAsString(data));
}
Outputs:
{"is_charging":true}
Note that only objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false); is required in your case. Others are provided for reference in case you need them.
It is possible by changing the attribute name from isCharging to charging
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean charging;
}
Result:
{"is_charging": true}
AUTO_DETECT_IS_GETTERS is a mapper feature that determines whether "is getter" methods are automatically detected based on standard Bean naming convention or not. If yes, then all public zero-argument methods that start with prefix "is", and whose return type is boolean are considered as "is getters". If disabled, only methods explicitly annotated are considered getters.
By default the feature is enabled. You can disable it while configuring your object mapper. Use,
disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
which is method in ObjectMapper class
i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!