How to deserialize JSON to interface? - java

I have trouble with deserialization JSON to some of classes ChildA, ChildB and etc. that implements Basic interface in following example.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = InstagramUser.class, name = "ChildA")
})
public interface Basic {
getName();
getCount();
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildA")
public class ChildA implements Basic { ... }
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildB")
public class ChildB implements Basic { ... }
...
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response<E extends Basic> {
#JsonProperty("data")
private List<E> data;
public List<E> getData() {
return data;
}
public void setData(List<E> data) {
this.data = data;
}
}
// deserialization
HTTPClient.objectMapper.readValue(
response,
(Class<Response<ChildA>>)(Class<?>) Response.class
)
Exception is: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id (for class Basic)
Expected JSON is like this:
{
"data": [{ ... }, ...]
}
There is no property that is presented in all type objects so they are completely different. But as you can see on readValue line I know what is expected type. How to structure JsonTypeInfo and JsonSubTypes annotaions to deserialize JSON as expected class?

I kinda had the same problem as you, based in the reading here: Jackson Deserialize Abstract Classes I created my own solution, it basically consists of creating my own deserializer, the trick is to use/identify a specific property within JSON to know which instance type should be returned from deserialization, example is:
public interface Basic {
}
First Child:
public class ChildA implements Basic {
private String propertyUniqueForThisClass;
//constructor, getters and setters ommited
}
SecondChild:
public class ChildB implements Basic {
private String childBUniqueProperty;
//constructor, getters and setters ommited
}
The deserializer (BasicDeserializer.java) would be like:
public class BasicDeserializer extends StdDeserializer<Basic> {
public BasicDeserializer() {
this(null);
}
public BasicDeserializer(final Class<?> vc) {
super(vc);
}
#Override
public Basic deserialize(final JsonParser jsonParser,
final DeserializationContext deserializationContext)
throws IOException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
// look for propertyUniqueForThisClass property to ensure the message is of type ChildA
if (node.has("propertyUniqueForThisClass")) {
return mapper.treeToValue(node, ChildA.class);
// look for childBUniqueProperty property to ensure the message is of type ChildB
} else if (node.has("childBUniqueProperty")) {
return mapper.treeToValue(node, ChildB.class);
} else {
throw new UnsupportedOperationException(
"Not supported class type for Message implementation");
}
}
}
Finally, you'd have an utility class (BasicUtils.java):
private static final ObjectMapper MAPPER;
// following good software practices, utils can not have constructors
private BasicUtils() {}
static {
final SimpleModule module = new SimpleModule();
MAPPER = new ObjectMapper();
module.addDeserializer(Basic.class, new BasicDeserializer());
MAPPER.registerModule(module);
}
public static String buildJSONFromMessage(final Basic message)
throws JsonProcessingException {
return MAPPER.writeValueAsString(message);
}
public static Basic buildMessageFromJSON(final String jsonMessage)
throws IOException {
return MAPPER.readValue(jsonMessage, Basic.class);
}
For testing:
#Test
public void testJsonToChildA() throws IOException {
String message = "{\"propertyUniqueForThisClass\": \"ChildAValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildA);
System.out.println(basic);
}
#Test
public void testJsonToChildB() throws IOException {
String message = "{\"childBUniqueProperty\": \"ChildBValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildB);
System.out.println(basic);
}
The source code can be found on: https://github.com/darkstar-mx/jsondeserializer

I find not exactly solution but a workaround. I used custom response class ChildAResponse and passed it to ObjectMapper.readValue() method.
class ChildAResponse extends Response<ChildA> {}
// deserialization
HTTPClient.objectMapper.readValue(
response,
ChildAResponse.class
)
So JsonTypeInfo and JsonSubTypes annotations on the interface are no longer needed.

Related

How deserialize plain String to Json using Jackson in Java?

I have a simple class as property of mage:
// getter/setter omitted for brevity
public class Magic() {
String Spell;
int strength;
}
public class Mage() {
String name;
Magic magic;
}
I need to deserialize JSON from 2 different source strings:
{
"name" : "Sauron",
"magic" : {
"spell" : "Tamador",
"strenght" : 10
}
}
and
{
"name" : "Gandalf",
"magic" : "You shall not pass"
}
or even "You shall not pass" -> Magic object
I thought going with #JsonDeserialize(using = MagicDeserializer.class) would be the way to go with Jackson, but the Parser barfs with "Unrecognized token". Is there a way I can intercept the loading to do my own parsing?
The idea of a custom deserializer is correct, you can extends the StdDeserializer class and in its deserialize method convert the json to a JsonNode separating the two Stringand Object distinct values associated to the magic key in the json:
public class MagicDeserializer extends StdDeserializer<Magic> {
public MagicDeserializer() {
super(Magic.class);
}
#Override
public Magic deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
final ObjectCodec codec = jp.getCodec();
JsonNode root = codec.readTree(jp);
Magic magic = new Magic();
if (root.isTextual()) { //<- magic is a string
magic.setSpell(root.textValue());
return magic;
}
//ok, so magic is an Magic object
return codec.treeToValue(root, Magic.class);
}
}
Then if you annotate your Magic field you can deserialize both the jsons:
#Data
public class Mage {
private String name;
#JsonDeserialize(using = MagicDeserializer.class)
private Magic magic;
}
#Data
public class Magic {
private String Spell;
private int strength;
}
Mage sauron = mapper.readValue(json1, Mage.class);
System.out.println(mapper.writeValueAsString(sauron));
Mage gandalf = mapper.readValue(json2, Mage.class);
System.out.println(mapper.writeValueAsString(gandalf));

Lombok with JsonDeserializer

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
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);
}
}
}
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.

Getting Spring/Jackson to intelligently deserialize model subclasses

Given a model hierarchy like so:
// WARNING: This is pseudo-code for giving an example!
public abstract class BaseVehicle {
private String make;
private String model;
// Constructors, getters & setters down here
}
public class Motorcycle extends BaseVehicle {
private int numCylinders;
// Constructors, getters & setters down here
}
public class Car extends BaseVehicle {
// ...etc.
}
And given the following payload class (that will be sent to a Spring controller):
public class Payload {
#JsonIgnore
#JsonProperty(value = "orgId")
private String orgId;
#JsonIgnore
#JsonProperty(value = "isInitialized")
private Boolean isInitialized;
#JsonIgnore
#JsonProperty(value = "vehicle")
private BaseVehicle vehicle;
// Constructors, getters & setters down here
}
I'm wondering if its possible to have the Spring controller (using Jackson for JSON serialization) configured to only expect a BaseVehicle instance in the Payload it receives, but to dynamically infer which BaseVehicle subclass was actually sent:
#RequestMapping(value='/payload', method=RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
#ResponseBody MyAppResponse onPayload(#RequestBody Payload payload) {
logger.info("Received a payload with a vehicle of type: " + payload.getVehicle().getClass().getName());
}
So that if I happen to send a Payload JSON that contains a Motorcycle as its vehicle field, then when that logger.info(...) statement fires, the code sees the vehicle is a Motorcycle (and ditto for any other BaseVehicle subclass)?
Is this possible, if so, how?
However I'd greatly prefer a solution that allows the JSON to remain as-is.
As I mentioned in my comment above, you could analyze the payload vehicle JSON object tree in order to make a little analysis trying to detect the payload element type.
#JsonDeserialize(using = BaseVehicleJsonDeserializer.class)
abstract class BaseVehicle {
#JsonProperty
private String make;
#JsonProperty
private String model;
}
#JsonDeserialize(as = Car.class)
final class Car
extends BaseVehicle {
}
#JsonDeserialize(as = Motorcycle.class)
final class Motorcycle
extends BaseVehicle {
#JsonProperty
private int numCylinders;
}
The trick here is the #JsonDeserialize annotation. The BaseVehicleJsonDeserializer can be implemented as follows:
final class BaseVehicleJsonDeserializer
extends JsonDeserializer<BaseVehicle> {
#Override
public BaseVehicle deserialize(final JsonParser parser, final DeserializationContext context)
throws IOException {
final TreeNode treeNode = parser.readValueAsTree();
final Class<? extends BaseVehicle> baseVehicleClass = Stream.of(treeNode)
// Check if the tree node is ObjectNode
.filter(tn -> tn instanceof ObjectNode)
// And cast
.map(tn -> (ObjectNode) tn)
// Now "bind" the object node with if the object node can be supported by the resolver
.flatMap(objectNode -> Stream.of(BaseVehicleTypeResolver.cachedBaseVehicleTypeResolvers).filter(resolver -> resolver.matches(objectNode)))
// If found, just get the detected vehicle class
.map(BaseVehicleTypeResolver::getBaseVehicleClass)
// Take the first resolver only
.findFirst()
// Or throw a JSON parsing exception
.orElseThrow(() -> new JsonParseException(parser, "Cannot parse: " + treeNode));
// Convert the JSON tree to the resolved class instance
final ObjectMapper objectMapper = (ObjectMapper) parser.getCodec();
return objectMapper.treeToValue(treeNode, baseVehicleClass);
}
// Known strategies here
private enum BaseVehicleTypeResolver {
CAR_RESOLVER {
#Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Car.class;
}
#Override
protected boolean matches(final ObjectNode objectNode) {
return !objectNode.has("numCylinders");
}
},
MOTORCYCLE_RESOLVER {
#Override
protected Class<? extends BaseVehicle> getBaseVehicleClass() {
return Motorcycle.class;
}
#Override
protected boolean matches(final ObjectNode objectNode) {
return objectNode.has("numCylinders");
}
};
// Enum.values() returns array clones every time it's invoked
private static final BaseVehicleTypeResolver[] cachedBaseVehicleTypeResolvers = BaseVehicleTypeResolver.values();
protected abstract Class<? extends BaseVehicle> getBaseVehicleClass();
protected abstract boolean matches(ObjectNode objectNode);
}
}
As you can see, such an approach is more or less fragile and sophisticated, but it tries to make some analysis. Now, how it can be used:
final ObjectMapper mapper = new ObjectMapper();
Stream.of(
"{\"orgId\":\"foo\",\"isInitialized\":true,\"vehicle\":{\"make\":\"foo\",\"model\":\"foo\"}}",
"{\"orgId\":\"bar\",\"isInitialized\":true,\"vehicle\":{\"make\":\"bar\",\"model\":\"bar\",\"numCylinders\":4}}"
)
.map(json -> {
try {
return mapper.readValue(json, Payload.class);
} catch ( final IOException ex ) {
throw new RuntimeException(ex);
}
})
.map(Payload::getVehicle)
.map(BaseVehicle::getClass)
.forEach(System.out::println);
Output:
class q43138817.Car
class q43138817.Motorcycle

How to serialize JSON with array field to object with String field?

I have a JSON object like
{
"id" : "1",
"children" : ["2","3"]
}
And I have a Java object like (constructor, getters and setters are omitted):
public class Entity {
public String id;
public String children;
}
I want this JSON to be deserialized to my Java object by this code using Jackson:
Entity entity = mapper.readValue(json, Entity.class);
But get the following error:
Can not deserialize instance of java.lang.String out of START_ARRAY token
How can I solve it without changing type of children field?
The children field is expected to have the following value: ["2","3"].
Creating a custom deserializer
Create a custom deserializer to get the raw JSON value. You can choose one of the following implementations, according to your needs:
It will give you the JSON as is, that is, keeping all the spaces and tabs:
public class RawJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
long begin = jp.getCurrentLocation().getCharOffset();
jp.skipChildren();
long end = jp.getCurrentLocation().getCharOffset();
String json = jp.getCurrentLocation().getSourceRef().toString();
return json.substring((int) begin - 1, (int) end);
}
}
It will give you the JSON without extra spaces and tabs:
public class RawJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
return mapper.writeValueAsString(node);
}
}
Annotate your class to use the deserializer defined above
Change the Entity class by annotating the children attribute with #JsonDeserialize referencing the deserializer defined above:
public class Entity {
public String id;
#JsonDeserialize(using = RawJsonDeserializer.class)
public String children;
}
Parsing the JSON
Then parse the JSON using ObjectMapper and Jackson will use your custom deserializer:
String json = "{\"id\":\"1\",\"children\":[\"2\",\"3\"]}";
ObjectMapper mapper = new ObjectMapper();
Entity entity = mapper.readValue(json, Entity.class);
The value of the children attribute will be ["2","3"].
For more details, have a look at this question.
Marshall your objects into JSON format.
Then Unmarshall from the JSON file
public interface MarshallingSupport {
public String marshal(Object object);
public <T> T unmarshal(String s, Class<T> t);
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JacksonJSONMarshallingSupport implements MarshallingSupport {
private final ObjectMapper mapper;
public JacksonJSONMarshallingSupport(ObjectMapper mapper) {
this.mapper = mapper;
this.mapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
}
#Override
public String marshal(Object object) {
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException ex) {
throw new RuntimeException(ex);
}
}
#Override
public <T> T unmarshal(String s, Class<T> t) {
try {
T newObj = mapper.readValue(s, t);
return newObj;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Taking the #Cassio's answer and if you don't want to or you can't annotate your Entity class, just add some configurations.
First create an abstract class [for method annotation purpose you can create an interface, but in this case we will annotate a bean property so we create an abstract class, and if you also want to annotate a method in this abstract class you have to declare that method as abstract] that will be like a mime bean for Jackson configurations:
public abstract class EntityMixIn {
#JsonDeserialize(using = RawJsonDeserializer.class)
public String children;
}
Now, you have to tell your mapper to take this mixin class and act like the original Entity class just for this configuration purpose:
mapper.addMixIn(Entity.class, EntityMixIn.class);

Using #JsonIdentityInfo without annotations

I use Jackson 2.2.3 to serialize POJOs to JSON. Then I had the problem, that I couldn't serialize recursive structures...I solved this problem by using #JsonIdentityInfo => works great.
But, I don't want this annotation on the top of my POJO.
So my question is: Is there any other possibility to set the default behavior of my ObjectMapper to use the feature for every POJO?
So I want to transform this annotation code
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
to something like
ObjectMapper om = new ObjectMapper();
om.setDefaultIdentityInfo(ObjectIdGenerators.IntSequenceGenerator.class, "#id");
Any ideas?
You can achieve that using the Jackson mix-in annotations or the Jackson annotation introspector.
Here is an example showing both methods:
public class JacksonJsonIdentityInfo {
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
static class Bean {
public final String field;
public Bean(final String field) {this.field = field;}
}
static class Bean2 {
public final String field2;
public Bean2(final String field2) {this.field2 = field2;}
}
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id2")
static interface Bean2MixIn {
}
static class Bean3 {
public final String field3;
public Bean3(final String field3) {this.field3 = field3;}
}
static class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public ObjectIdInfo findObjectIdInfo(final Annotated ann) {
if (ann.getRawType() == Bean3.class) {
return new ObjectIdInfo(
PropertyName.construct("#id3", null),
null,
ObjectIdGenerators.IntSequenceGenerator.class,
null);
}
return super.findObjectIdInfo(ann);
}
}
public static void main(String[] args) throws JsonProcessingException {
final Bean bean = new Bean("value");
final Bean2 bean2 = new Bean2("value2");
final Bean3 bean3 = new Bean3("value3");
final ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Bean2.class, Bean2MixIn.class);
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
System.out.println(mapper.writeValueAsString(bean));
System.out.println(mapper.writeValueAsString(bean2));
System.out.println(mapper.writeValueAsString(bean3));
}
}
Output:
{"#id":1,"field":"value"}
{"#id2":1,"field2":"value2"}
{"#id3":1,"field3":"value3"}
After several months and a lot of research, I've implemented my own solution to keep my domain clear of jackson dependencies.
public class Parent {
private Child child;
public Child getChild(){return child;}
public void setChild(Child child){this.child=child;}
}
public class Child {
private Parent parent;
public Child getParent(){return parent;}
public void setParent(Parent parent){this.parent=parent;}
}
First, you have to declare each of your entities of the bidirectional relationship:
public interface BidirectionalDefinition {
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Parent.class)
public interface ParentDef{};
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Child.class)
public interface ChildDef{};
}
After that, the object mapper can be automatically configured:
ObjectMapper om = new ObjectMapper();
Class<?>[] definitions = BidirectionalDefinition.class.getDeclaredClasses();
for (Class<?> definition : definitions) {
om.addMixInAnnotations(definition.getAnnotation(JsonIdentityInfo.class).scope(), definition);
}

Categories