Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested custom deserialization in java with jackson

i've been wondering how to correctly solve this problem

I have a data model like this:

Class B
 String fieldB1;
 String fieldB2;

Class A
 String fieldA1;
 String fieldA2;
 List<B> fieldA3;

(and then another third class which has the same hierarchy as the other with fields and a list of A objects, but for simplicity let's stick with A and B)

Now on the other side i have to deserialize these classes in classes with the same name and params just with different data types

So ^ must read as:

Class B
 int fieldB1;
 double fieldB2;

Class A
 float fieldA1;
 float fieldA2;
 List<B> fieldA3;

Since i'm not experienced, my first guess was to write customs Deserializer in jackson for A and B, and when I deserialize a Class like B which does not have reference to other classes with custom deserialization methods, the conversion is easy.

But what about creating a custom deserializer for Class A? When I have to deserialize the fieldA3, aka the list of B objects, how should I operate? Should try to call in some way in the ClassACustomDeserializer the ClassBCustomDeserializer? How to do that? Or is there another simpler solution to just tell jackson to transform some String fields in some other types based on my personal mapping?

This is how i would deserialize B

public class BDeserializer extends StdDeserializer<B> { 

    public BDeserializer() { 
        this(null); 
    } 

    public BDeserializer(Class<?> vc) { 
        super(vc); 
    }

    @Override
    public B deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);

        int fieldB1= node.findValue("fieldB1").asInt();
        double fieldB2= node.findValue("fieldB2").asDouble();

        return new B(fieldB1,fieldB2);
    }
}
like image 938
ro.nin Avatar asked Mar 09 '23 03:03

ro.nin


1 Answers

Jackson is smart enough to convert the text value to an appropriate numeric type, so it should be able to deserialize a JSON like:

{ "fieldB1": 10, "fieldB2" : "0.333" }

to your

Class B
    int fieldB1;
    double fieldB2;

just nice, even w/o using a custom deserializer.

If you want to stick with a custom deserializer, for whatever reason, you can either use JsonNode.traverse() to create a sub-parser:

JsonParser parser = node.findValue("fieldA3").traverse();
parser.setCodec(jp.getCodec());
List<B> list = parser.readValueAs(new TypeReference<List<B>>() {});

or navigate the token stream yourself, instead of using find:

while(jp.nextToken() != JsonToken.END_OBJECT) {
    if(jp.currentToken() == JsonToken.FIELD_NAME) {
       switch (jp.getCurrentName()) {
           //...
           case "fieldA3":
             jp.nextToken();
             list=jp.readValueAs(new TypeReference<List<ClassB>>() {}));
           break;
       }
    }
}

the latter should be more efficient, if performance is of concern.

like image 196
zeppelin Avatar answered Mar 19 '23 20:03

zeppelin