Home > Back-end >  Lombok with JsonDeserializer
Lombok with JsonDeserializer

Time:10-24

I have a JSON string that I want to deserialize into a class. The JSON looks like so:

{ "data": { "name": "Box 1", "size": "10x20" } }

I can deserialize this into the following class:

@Builder
@Value
@JsonDeserialize(builder = Box1.Box1Builder.class)
public class Box1 {

    @JsonProperty("data")
    Box1Data data;

    public static Box1 of(String json) throws IOException {
        return new ObjectMapper().readValue(json, Box1.class);
    }

    @Builder
    @Value
    @JsonDeserialize(builder = Box1Data.Box1DataBuilder.class)
    static class Box1Data {

        @JsonProperty("name")
        String name;

        @JsonProperty("size")
        String size;

    }

}

The above class looks clumsy since it has a useless hierarchy of data. I can get rid of it like so:

@Builder
@Value
@JsonDeserialize(using = Box2Deserializer.class)
public class Box2 {

    @JsonProperty("name")
    String name;

    @JsonProperty("size")
    String size;

    public static Box2 of(String json) throws IOException {
        return new ObjectMapper().readValue(json, Box2.class);
    }

    static class Box2Deserializer extends JsonDeserializer<Box2> {

        @Override
        public Box2 deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
            var node = jsonParser.getCodec().readTree(jsonParser);
            var dataNode = node.get("data");
            return Box2.builder()
                    .name(dataNode.get("name").toString())
                    .size(dataNode.get("size").toString())
                    .build();
        }
    }

}

But here, I hit a dead-end. I want the size field to be parsed into a Dimension instance. I can write a custom deserializer for size that parses a String and returns a proper Dimension, but I cannot use it via field annotations (@JsonDeserialize(using = SizeDeserializer.class) since the presence of JsonDeserialize class annotation forces it to be ignored in the case for Box1, and in the case for Box2, it's ignored cuz I'm building the box manually.

Is there an elegant solution to all this mess? What I want is to read the given JSON into a class like this:

@Builder
@Value
public class Box3 {

    @JsonProperty("name")
    String name;

    @JsonProperty("size")
    Dimension size;

    public static Box3 of(String json) {
        ...
    }

}

Thanks!

Asim

CodePudding user response:

I will add to @Iprakashv solution, besides only the needs for the JsonRootName type annotation and mapper serialization / deserialization for root node wrapping, you only need a custom type converter from a raw type to your custom type:

@Builder
@Value
@JsonRootName("data")
public class Box {

    @JsonProperty("name")
    String name;

    @JsonDeserialize(converter = StringToDimensionConverter.class)
    @JsonProperty("size")
    Dimension size;

    public static Box of(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
        mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
        return mapper.readValue(json, Box.class);
    }

    private static class StringToDimensionConverter extends StdConverter<String, Dimension> {
        @Override
        public DataWrapper.Box1Data.Dimension convert(String s) {
            return new DataWrapper.Box1Data.Dimension(s);
        }
    }
}

CodePudding user response:

You actually do not need a custom deserializer and the @JsonDeserialize annotation. The ObjectMapper provides a configuration to enable wrapping/unwrapping a root value which can be provided using the @JsonRootName annotation over the Wrapper object class.

@Builder
@Value
@JsonRootName("data")
public class Box {

    @JsonProperty("name")
    String name;

    @JsonProperty("size")
    String size;

    public static Box of(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
        return mapper.readValue(json, Box.class);
    }
}

PS: Totally missed the Dimension part in the question, for that, you can use a custom deserializer as mentioned in other answer.

  • Related