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.