I have a result from a web service that returns either a boolean value or a singleton map, e.g.
Boolean result:
{
id: 24428,
rated: false
}
Map result:
{
id: 78,
rated: {
value: 10
}
}
Individually I can map both of these easily, but how do I do it generically?
Basically I want to map it to a class like:
public class Rating {
private int id;
private int rated;
...
public void setRated(?) {
// if value == false, set rated = -1;
// else decode "value" as rated
}
}
All of the polymorphic examples use @JsonTypeInfo
to map based on a property in the data, but I don't have that option in this case.
@JsonProperty("rated")
public void setRating(JsonNode ratedNode) {
JsonNode valueNode = ratedNode.get("value");
// if the node doesn't exist then it's the boolean value
if (valueNode == null) {
// Use a default value
this.rating = -1;
} else {
// Convert the value to an integer
this.rating = valueNode.asInt();
}
}
ObjectMapper is the most important class in Jackson API that provides readValue() and writeValue() methods to transform JSON to Java Object and Java Object to JSON. ObjectMapper class can be reused and we can initialize it once as Singleton object.
Jackson is a solid and mature JSON serialization/deserialization library for Java. The ObjectMapper API provides a straightforward way to parse and generate JSON response objects with a lot of flexibility.
The @JsonSetter annotation tells Jackson to deserialize the JSON into Java object using the name given in the setter method. Use this annotation when your JSON property names are different to the fields of the Java object class, and you want to map them.
Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls.
No no no. You do NOT have to write a custom deserializer. Just use "untyped" mapping first:
public class Response {
public long id;
public Object rated;
}
// OR
public class Response {
public long id;
public JsonNode rated;
}
Response r = mapper.readValue(source, Response.class);
which gives value of Boolean
or java.util.Map
for "rated" (with first approach); or a JsonNode
in second case.
From that, you can either access data as is, or, perhaps more interestingly, convert to actual value:
if (r.rated instanced Boolean) {
// handle that
} else {
ActualRated actual = mapper.convertValue(r.rated, ActualRated.class);
}
// or, if you used JsonNode, use "mapper.treeToValue(ActualRated.class)
There are other kinds of approaches too -- using creator "ActualRated(boolean)", to let instance constructed either from POJO, or from scalar. But I think above should work.
You have to write your own deserializer. It could look like this:
@SuppressWarnings("unchecked")
class RatingJsonDeserializer extends JsonDeserializer<Rating> {
@Override
public Rating deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, Object> map = jp.readValueAs(Map.class);
Rating rating = new Rating();
rating.setId(getInt(map, "id"));
rating.setRated(getRated(map));
return rating;
}
private int getInt(Map<String, Object> map, String propertyName) {
Object object = map.get(propertyName);
if (object instanceof Number) {
return ((Number) object).intValue();
}
return 0;
}
private int getRated(Map<String, Object> map) {
Object object = map.get("rated");
if (object instanceof Boolean) {
if (((Boolean) object).booleanValue()) {
return 0; // or throw exception
}
return -1;
}
if (object instanceof Map) {
return getInt(((Map<String, Object>) object), "value");
}
return 0;
}
}
Now you have to tell Jackson to use this deserializer for Rating
class:
@JsonDeserialize(using = RatingJsonDeserializer.class)
class Rating {
...
}
Simple usage:
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.readValue(json, Rating.class));
Above program prints:
Rating [id=78, rated=10]
for JSON:
{
"id": 78,
"rated": {
"value": 10
}
}
and prints:
Rating [id=78, rated=-1]
for JSON:
{
"id": 78,
"rated": false
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With