Jackson's #JsonPropertyOrder doesn't work with #JsonUnwrapped - java

I have an object that contains another object attribute like this:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
#JsonPropertyOrder({"fA1","b","fA2"})
#Data
public class A {
private String fA1;
private String fA2;
#JsonUnwrapped
private B b = new B();
#Data
class B {
private String fB1;
private String fB2;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
A a = new A ();
System.out.println(objectMapper.writeValueAsString(a));
}
}
what i want is generate json that respect this order :
{
"fA1":"",
"fB1":"",
"fA2":"",
"fB2":""
}
Is there any way to do this?

According to this issue in the jackson-databind repository on GitHub, the #JsonPropertyOrder annotation doesn't work with #JsonUnwrapped annotation. See the quote below:
True, unwrapped properties are not included in ordering, since they are not really known by enclosing serializer as regular properties. And specifically, as things are, will be output as a block of properties per contained, so even if known they can not be reordered separately.
Perhaps something could be done with Jackson 3.x once we get there.
But you may consider a workaround: as you seem to be using Lombok, you could annotate b with #Delegate and #JsonIgnore:
#Data
#JsonPropertyOrder({"fa1", "fb1", "fa2", "fb2"})
public class A {
private String fA1;
private String fA2;
#Delegate
#JsonIgnore
private B b = new B();
}
The #JsonIgnore annotation will ensure that the b property is not serialized by Jackson.
And the #Delegate annotation will tell Lombok to generate delegate methods that forward the call to the B methods. With that, the A class will have getters and setters that are delegated to the getters and setters of the fB1 and fB2 fields.

Related

Jackson json only convert selected fields and methods

With jackson there is a way to ignore some fields using #JsonIgnore. Is there a way to do the opposite, and only show fields with are annotated? I'm working with an external class with a lot of fields and I only want to select a small subset of them. I'm getting tons of recursion problems (using some type of ORM) where object A -> B -> A -> B -> A .... which are not even necessary to export.
You can configure the object mapper to ignore absolutely everything unless specified by JsonProperty,
public class JacksonConfig {
public static ObjectMapper getObjectMapper(){
//The marshaller
ObjectMapper marshaller = new ObjectMapper();
//Make it ignore all fields unless we specify them
marshaller.setVisibility(
new VisibilityChecker.Std(
JsonAutoDetect.Visibility.NONE,
JsonAutoDetect.Visibility.NONE,
JsonAutoDetect.Visibility.NONE,
JsonAutoDetect.Visibility.NONE,
JsonAutoDetect.Visibility.NONE
)
);
//Allow empty objects
marshaller.configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false );
return marshaller;
}
}
public class MyObject {
private int id;
#JsonProperty
private String name;
private Date date;
//Getters Setters omitted
in this case only name would be serialized.
Sample repo, https://github.com/DarrenForsythe/jackson-ignore-everything
Yes definitely you can; Create a class with only the feilds you need and add the below property in the object mapper and rest is done.
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES to false
You can use #JsonIgnoreProperties(ignoreUnknown=true) on the pojo class so only the fields which are available in the pojo class will be mapped and resf will be left out.
For example
Json data
{
"name":"Abhishek",
"age":30,
"city":"Banglore",
"state":"Karnatak"
}
pojo class
#JsonIgnoreProperties(ignoreUnknown=true)
Class Person{
private int id;
private String name;
private String city;
}
Here state in not present in the Person class so that field won't be mapped

How to not write Option.None with jackson objectMapper (and read it)?

I use jackson ObjectMapper to serialize and deserialize some data of mine, which have fields of javaslang Option type. I use JavaslangModule (and Jdk8Module). And when it write the json, Option.None value fields are written as null.
To reduce the json size and provide some simple backward compatibility when later adding new fields, what I want is that:
fields with Option.None value are simply not written,
missing json fields that correspond to data model of Option type, be set to Option.None upon reading
=> Is that possible, and how?
Note:
I think that not-writing/removing null json fields would solve (1). Is it possible? And then, would reading it works (i.e. if model field with Option value is missing in the json, set it None?
Luckily there is a much simpler solution.
1) In your ObjectMapper configuration, set serialization inclusion to only include non absent field:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(vavr());
objectMapper.setSerializationInclusion(NON_ABSENT);
return objectMapper;
}
2) Set the default value of your optional fields to Option.none:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Foo {
private Option<String> bar = Option.none(); // If the JSON field is null or not present, the field will be initialized with none
}
That's it!
And the even better news is that it works for all Iterables, not just for Option. In particular it also works for Vavr List type!
I found a solution that works with immuatble (lombok #Value) models:
add a filter on all Object using mixIn that doesn't write Option.None (see "the solution" below)
my existing ObjectMapper (with JavaslangModule) is already setting None to Option field when the corresponding json entry is missing
The code
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import javaslang.control.Option;
import javaslang.jackson.datatype.JavaslangModule;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Field;
public class JsonModelAndSerialization {
// Write to Json
// =============
private static ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new Jdk8Module())
.registerModule(new JavaslangModule())
// not required but provide forward compatibility on new field
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
static String write(Object data) throws JsonProcessingException {
SimpleBeanPropertyFilter filter = new NoneOptionPropertyFilter();
objectMapper.addMixIn(Object.class, NoneOptionFilter.class);
final SimpleFilterProvider filters = new SimpleFilterProvider().setDefaultFilter(filter);
ObjectWriter writer = objectMapper.writer(filters);
return writer.writeValueAsString(data);
}
// Filter classes
// ==============
#JsonFilter("Filter None")
private static class NoneOptionFilter {}
private static class NoneOptionPropertyFilter extends SimpleBeanPropertyFilter {
#Override
public void serializeAsField(
Object pojo, JsonGenerator jgen,
SerializerProvider provider, PropertyWriter writer) throws Exception{
Field field = pojo.getClass().getDeclaredField(writer.getName());
if(field.getType().equals(Option.class)){
field.setAccessible(true);
Option<?> value = (Option<?>) field.get(pojo);
if(value.isEmpty()) return;
}
super.serializeAsField(pojo, jgen, provider, writer);
}
}
// Usage example
// =============
// **important note**
// For #Value deserialization, a lombok config file should be added
// in the source folder of the model class definition
// with content:
// lombok.anyConstructor.addConstructorProperties = true
#Value
#AllArgsConstructor(onConstructor_={#JsonCreator})
public static class StringInt {
private int intValue;
private Option<String> stringValue;
}
#Value
#AllArgsConstructor(onConstructor_={#JsonCreator})
public static class StringIntPair {
private StringInt item1;
private StringInt item2;
}
#Test
public void readWriteMyClass() throws IOException {
StringIntPair myClass = new StringIntPair(
new StringInt(6 * 9, Option.some("foo")),
new StringInt( 42, Option.none()));
String json = write(myClass);
// {"item1":{"intValue":54,"stringValue":"foo"},"item2":{"intValue":42}}
StringIntPair myClass2 = objectMapper.readValue(json, StringIntPair.class);
assertThat(myClass2).isEqualTo(myClass);
}
}
The advantages:
reduce size of json when having Option.None (thus adding Option fields in the model doesn't cost size when not used)
it provides backward reading compatibility when later adding field with Option type in the model (which will default to None)
The disadvantage:
It is not possible to differentiate correct data with None field value and incorrect data where the field has erroneously been forgotten. I think this is quite acceptable.

Jackson: conflicting #JsonTypeInfo and #JsonSerialize(as=Klass.class)

The problem
I need to polymorphically JSON-(de-)serialize an #Autowired Spring bean (I'm using Spring Boot 2.0.4) using only original properties.
Since the bean is "enhanced", it is a subclass of my "original" bean, with class name ending with something like $$EnhancerBySpringCGLIB$$12345.
Tried so far
To avoid Jackson trying to serialize the "enhanced" part, I've declared my bean as a supertype of itself with
#JsonSerialize(as=MyClass.class)
It worked as intended.
But, when I try to do polymorphic serialization with
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = As.WRAPPER_OBJECT)
placed on the interface that the said class implements, the key of the wrapper object is the name of the enhanced class! The rest of JSON string is OK, that is, only properties of the "original" class are included. Needless to say, I can't de-serialize it now, since the mentioned subclass is not around any more.
Using JsonTypeInfo.Id.NAME defeats the whole idea of polymorphic deserialisation, IMHO. I can figure out the target class by querying ApplicationContext, if nothing else works.
EDIT:
Here is a Foo Bar example:
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = As.WRAPPER_OBJECT)
public class Foo {
private String foo = "Foo";
#JsonSerialize(as = Bar.class)
public static class Bar extends Foo {
private String bar = "Bar";
}
public static class UnwantedMutant extends Bar {
private String aThing = "Not welcome";
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
UnwantedMutant mutant = new UnwantedMutant();
System.out.println(mapper.writeValueAsString(mutant));
}
}
This prints
{"mypackage.Foo$UnwantedMutant":{"foo":"Foo","bar":"Bar"}}
while
{"mypackage.Foo$Bar":{"foo":"Foo","bar":"Bar"}}
is expected/desired.
So, the question:
is there any solution to this problem with "pure" Jackson means, or I just have to live with it?
Did you try with:
#JsonRootName(value = "NameOfYourClass") ?
Sorry if I didn't understand your question.

Deserializing cyclic JSON with only one class involved with Jackson

Is it possible to deserialize the following class with Jackson?
So the original version of the question wasn't entirely accurate. Here's a minimal example to reproduce the problem.
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class Thing {
public Thing thing;
#JsonCreator
public Thing(#JsonProperty("thing") Thing thing) {
this.thing = thing;
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Thing cyclic = new Thing(null);
cyclic.thing = cyclic;
String serialised = mapper.writeValueAsString(cyclic);
System.out.println(serialised);
Thing deserialised = mapper.readerFor(Thing.class).readValue(serialised);
System.out.println(deserialised.thing == deserialised);
}
}
This causes the unresolved forward reference exception. The issue seems to be that Jackson is told to use the annotated constructor, but it can't due to the cyclic dependency.
The solution is to add a default constructor, and remove the #JsonProperty and #JsonCreator annotations.

Jackson 3rd Party Class With No Default Constructor

I'm trying to use Jackson to read/write my POJOs to/from Json. As of right now, I've got it configured and working for my classes, except for a 3rd party class. When trying to read in the Json I get the error:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type
After a few quick google searches, it appears that my class needs either a default constructor or to override the default constructor with annotations. Unfortunately, the class in which this is failing is from a 3rd party library and that class does not have a default constructor and I obviously cannot over-write the code.
So my question is, is there anything I can do about this or am I just out of luck?
Thanks.
You could make use of Jackson's Mix-Ins feature, coupled with the Creator feature. The Mix-Ins feature alleviates the need to annotate the original third-party code, and the Creator feature provides a mechanism for custom instance creation.
For yet more customization, it's not too involved to write a custom deserializer.
One approach is to implement a custom JsonDeserializer to create the instance, annotating the fields of the type with #JsonDeserialize. One advantage of this approach over e.g. mixins is that it does not require modifying the ObjectMapper.
The StdNodeBasedDeserializer class allows mapping from a JsonNode representing the value to the desired type.
Type lacking a constructor
public class ThirdPartyType {
private String stringProperty;
private int intProperty;
private Object[] arrayProperty;
public ThirdPartyType(String a, int b, Object[] c) {
this.stringProperty = a;
this.intProperty = b;
this.arrayProperty = c;
}
// Getters and setters go here
}
Custom deserializer
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdNodeBasedDeserializer;
import java.io.IOException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
public class ThirdPartyTypeDeserializer
extends StdNodeBasedDeserializer<ThirdPartyType> {
protected ThirdPartyTypeDeserializer() {
super(ThirdPartyType.class);
}
#Override
public ThirdPartyType convert(JsonNode root, DeserializationContext ctxt)
throws IOException {
return new ThirdPartyType(
root.get("stringProperty").asText(null),
root.get("intProperty").asInt(),
StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
root.get("arrayProperty").elements(),
Spliterator.ORDERED),
false).toArray());
}
}
Type containing the third party type
public class EnclosingClass {
#JsonDeserialize(using = ThirdPartyTypeDeserializer.class)
private ThirdPartyType thirdPartyProperty;
// Getters and setters go here
}
Retrieving the value
String json = "{\"thirdPartyProperty\": {"
+ "\"stringProperty\": \"A\", "
+ "\"intProperty\": 5, "
+ "\"arrayProperty\": [1, \"B\", false]"
+ "}}";
ObjectMapper objectMapper = new ObjectMapper();
EnclosingClass enclosingClass =
objectMapper.readValue(json, EnclosingClass.class);

Categories

Resources