I need to deserialize some JSON to Java class. I have the following JSON:
{
"list": [[{
"type": "text",
"subType": "ss"
},
{
"type": "image",
"subType": "text"
}
]]
}
and I have the following Java classes:
public abstract class BaseClass {
public String type;
public String subType;
}
public class Text extends BaseClass {
...
}
public class Image extends BaseClass {
}
and I need deserialize in this way, if type equals image and subType equals text I need to deserialize into Text class otherwise I need deserialize to Image class.
How can I do it?
You don't need a custom deserializer. Mark your BaseClass with the following annotations, and deserialize with an ObjectMapper:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
#JsonSubTypes({#JsonSubTypes.Type(value = Text.class, name = "text"), #JsonSubTypes.Type(value = Image.class, name = "image")
})
public abstract class BaseClass {
public String type;
public String subType;
}
JsonTypeInfo defines to use value of type field for type name.
JsonSubTypes associates type names with java classes
You can implement your own deserializer like so:
public class BaseClassDeserializer extends StdDeserializer<BaseClass> {
public BaseClassDeserializer(Class<?> vc) {
super(vc);
}
#Override
public BaseClass deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String type = node.get("type").asText();
String subType = node.get("subType").asText();
if("image".equals(type) && "text".equals(subType)){
/* create Text class
return new Text */
} else {
/* create Image class
return new Image(args...) */
}
}
}
Related
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));
I want to do something like this with subtypes.
I have 3 different types of objects:
{
"value": "string"
}
{
"value": {
"type": "obj1"
}
}
{
"value": {
"type": "obj2"
}
}
value can either be a string or an object.
The corresponding Java classes are
public interface Value {
}
public class ValueString implements Value {
String value;
}
public abstract class ValueObj implements Value{
public String type;
}
public class ValueObj1 extends ValueObj {
private Obj1 value;
}
public class ValueObj2 extends ValueObj {
private Obj2 value;
}
I don't mind having a discriminator inside Obj1 and Obj2, but there is no place for one when the value is just a string. Is there a way that I can set this up so that if the value is a string, it deserializes to ValueString, but if it is an object, it deserializes to the correct ValueObj1 or ValueObj2?
It can be easily done by creating a custom deserializer first:
p.s. I assumed that there're only three types of objects as you posted.
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
this(null);
}
protected ValueDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Value deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
if (jsonNode.get("value").isValueNode()) {
return new ValueString(jsonNode.get("value").asText());
} else if ("obj1".equals(jsonNode.get("value").get("type").asText())) {
ValueObj1 valueObj1 = new ValueObj1();
// The logic to handle type obj1
return valueObj1;
} else {
ValueObj2 valueObj2 = new ValueObj2();
// The logic to handle type obj2
return valueObj2;
}
}
Then simply annotate class Value with #JsonDeserialize as follows:
#JsonDeserialize(using = ValueDeserializer.class)
public interface Value {
}
Finally, let Jackson do the rest for you:
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(objectMapper.readValue(jsonStr, Value.class)));
Is there a way to deserialize an enum which works for both the name and the object notation. I do want to keep the Shape as object for the deserialization though
e.g. This works for "type": {"name":"MYENUM"}, but what would I need to add to have it also work for "type": "MYENUM"
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyType {
#JsonProperty("MYENUM")
MYENUM("MyEnum")
public final String name = name();
MyType(String value) {
this.value = value;
}
#JsonCreator
public static MyType deserialize (#JsonProperty("name") String name) {
return MyType.valueOf(name);
}
}
Have tried adding a delegate like this
#JsonCreator(mode=JsonCreator.Mode.DELEGATING)
public static MyType deserializeString (String name) {
return MyType.valueOf(name);
}
One way to solve your problem is a custom deserializer extending StdDeserializer and inside of it check if your json file is in the form of "type": {"name":"MYENUM"} or {"type": "MYENUM"}. This checking can be obtained with the JsonNode#isObject method :
public class MyTypeDeserializer extends StdDeserializer<MyType> {
public MyTypeDeserializer() {
this(null);
}
public MyTypeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public MyType deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode root = jp.getCodec().readTree(jp);
JsonNode nodeType = root.get("type");
String name = nodeType.isObject() ? nodeType.get("name").asText() : nodeType.asText();
return MyType.valueOf(name);
}
}
Then you can annotate your MyType enum with the #JsonDeserialize(using = MyTypeDeserializer.class) deleting your deserialize method like below :
#JsonDeserialize(using = MyTypeDeserializer.class)
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyType { //omitted fields and methods for brevity }
I have a json payload (request payload of a rest api) with a defined schema, but there is one property that can take an array of unknown key value pairs. The value for each property can be of different type like number, string, array, range, date, etc. How do i create a POJO for this property and make deserialization work for the same?
I am currently thinking about writing a custom deserializer for my Property class, where i check the type of value and do some custom logic accordingly.
This looks like a typical requirement. I feel that there should be something available in Jackson or Gson that i am missing. I would love to reuse if it already exist. I looked around in SO, but couldnt find a good answer so far. Any suggestions would be appreciated.
{
"id": 1234,
"name": "test name 1",
"properties": [
{
"key_a": 100
},
{
"key_b": [
"string1",
"string2",
"string3"
]
},
{
"key_c": {
"range": {
"min": 100,
"max": 1000
}
}
}
]
}
I am thinking my POJO for property object would look something like this.
class Property {
private String key;
private Value value;
}
It is possible to use inheritance for that. This is the classes for your example with Jackson
public class Sample {
#JsonProperty(value = "id")
Integer id;
#JsonProperty(value = "name")
String name;
#JsonProperty(value = "properties")
List<Property> properties;
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = KeyA.class, name = "key_a"),
#JsonSubTypes.Type(value = KeyB.class, name = "key_b"),
#JsonSubTypes.Type(value = KeyC.class, name = "key_c")
})
public abstract class Property {
}
public class KeyA extends Property{
Integer value;
public KeyA(Integer value) {
this.value = value;
}
#JsonValue
public Integer getValue() {
return value;
}
}
public class KeyB extends Property {
List<String> valueList;
#JsonCreator
public KeyB( List<String> valueList) {
this.valueList = valueList;
}
#JsonValue
public List<String> getValueList() {
return valueList;
}
}
public class KeyC extends Property {
#JsonProperty(value = "range")
Range value;
}
public class Range {
#JsonProperty(value = "min")
Integer min;
#JsonProperty(value = "max")
Integer max;
}
If I understand correctly you want to change to JSON and back. I wrote a small class for my own SpringBoot project, using ObjectMapper
#Component
public final class JsonUtils {
private final ObjectMapper mapper;
#Autowired
public JsonUtils(ObjectMapper mapper) {
this.mapper = mapper;
}
public String asJsonString(final Object object) {
try {
return mapper.registerModule(new JavaTimeModule())
.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* Customized Objectmapper for reading values compatible with this class' other methods
* #Return the desired object you want from a JSON
* IMPORTANT! -your return object should be a class that has a #NoArgsConstructor-
*/
public Object readValue(final String input, final Class<?> classToRead) {
try {
return mapper
.readValue(input, classToRead);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}`
Perhaps it can be of some use to you.
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.