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

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

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

Automatic deserialization of String to Object with Jackson

Context
Say you have:
public class Dto {
private String name;
private String List<Custom> customs;
// getters and setters...
}
and
public class Custom {
private String something;
private String else;
// getters and setters...
}
Your Spring MVC RestController receives a list of Dto:
#PostMapping
public String create(#RequestBody #Valid List<Dto> dtos) {
return myService.process(features);
}
Input
However, you know that the client-side service which will send data to your controller will send something like this:
[
{
"name": "Bob",
"customs": [
"{\n \"something\": \"yes\",\n \"else\": \"no\"\n }"
]
}
]
Notice how the List<Custom> actually ends up being received as a List<String>. Please assume this cannot be changed on the client-side and we have to deal with it on the server-side.
Question
Is there a Jackson annotation which would automagically take the input String and try to serialize it into a Custom class?
Attempts
A few things that didn't work, including:
#JsonSerialize(using = ToStringSerializer.class)
private List<Custom> customs;
along with
public Custom(String json) {
try {
new ObjectMapper().readerFor(Custom.class).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
As it is, we have had to change the customs type to List<String> and add a utility method which converts a String into a Custom using an ObjectMapper. This is rather dissatisfying.
You need to implement custom deserialiser or converter which would be used to convert given payload to required type. One trick, you could use is to create new ObjectMapper and use it for internal deserialisation.
Example usage:
class CustomConverter extends StdConverter<String, Custom> {
private final ObjectMapper mapper = new ObjectMapper();
#Override
public Custom convert(String value) {
try {
return mapper.readValue(value, Custom.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(value);
}
}
}
class Dto {
private String name;
#JsonDeserialize(contentConverter = CustomConverter.class)
private List<Custom> customs;
}
You need to create a custom Deserializer.
public class CustomDeserializer extends StdDeserializer<Custom> {
public CustomDeserializer() {
this(null);
}
public CustomDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Custom deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String name = node.get("name").asText();
...
return new Custom(id, name, ...);
}
}
and register the deserializer on the Custom class:
#JsonDeserialize(using = CustomDeserializer.class)
public class Custom {
...
}

How to deserialize JSON to interface?

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.

Custom Deserialization using Jackson in Java

I am trying to implement a university project where I try to fetch values from two Json fields and map it to one pojo class.
Sample Json:
"event":[{"D17-32":0,"S10":"D"}]
Pojo class
public class Event {
#JsonDeserialize(using = SignalCustomDeserializer.class)
#JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
#JsonProperty("S10")
private Signal S10;
#JsonProperty("D17-32")
private String D17_32;
//Getter and setter implementation
}
Class which I need to serialize the fields to
public class Signal{
private String value;
private String detectorId;
private int detectorValue; //this value has to be fetched from another json
//Getter and setter implementation
}
Custom deserializer class
public class SignalCustomDeserializer extends JsonDeserializer {
#Override
public Signal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String signalId = jsonParser.getCurrentName();
String signalVal = jsonParser.getValueAsString();
String detectorVal = jsonParser.getValueAsString("D01-16");
Signal signal = new Signal();
signal.setValue(signalVal);
signal.setDetectorId(getDetectorId(signalId));
return signal;
}
}
I am able to get the signalId and signalValue but I am unable to get the value for the other field. I am unsure if its available in the JsonObject when the custom deserialization class is called.

Jackson cannot deserialize enum as object even if I add customized deserializer

I want to use Jackson JSON to serialize/deserialize a class containing an enum object. My class is:
class Bar {
#JsonProperty("rateType")
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
private ReturnedRateType rateType;
public ReturnedRateType getRateType() {
return rateType;
}
public void setRateType(ReturnedRateType rateType) {
this.rateType = rateType;
}
}
The enum class ReturnedRateType is defined as:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final String value) {
if (value != null) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (value.equalsIgnoreCase(type.value)) {
return type;
}
}
}
return null;
}
}
As you see, I added #JsonFormat annotation to tell Jackson to serialize this enum as POJO, and added #JsonCreator annotation to get a static factory method from given string to enum object. Since Jackson can only serialize but can't deserialize from object representation to enum, I added the following customized deserializer for the enum ReturnedRateType:
public class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());
if(type != null)
return type;
throw new JsonMappingException("invalid value for ReturnedRateType");
}
}
But when I tested deserialization from a JSON string to enum, I got the error. The JSON string is:
{"rateType": {"value": "AA"}}
My test code is:
#Test
public void RateTypeToEnum() {
String json = "{\"rateType\": {\"value\": \"AA\"}}";
System.out.println(json);
ObjectMapper mapper = new ObjectMapper();
Bar bar = null;
try {
bar = mapper.readValue(json, Bar.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(bar.getRateType());
}
I expect to see the output should be AA. But jp.getValueAsString() in my customized deserializer ReturnedRateTypeDeserializer is null during the execution:
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString()); //jp.getValueAsString() is null here!
Thus it returns error. So what is wrong here?
According to the Jackson 2.5.X documentation on the JsonFormat annotation the Shape.Object does not work for the enum deserialisation:
Enums: Shapes JsonFormat.Shape.STRING and JsonFormat.Shape.NUMBER can
be used to change between numeric (index) and textual (name or
toString()); but it is also possible to use JsonFormat.Shape.OBJECT
to serialize (but not deserialize).
I'd make the JsonCreator static method accept a JsonNode and read the string value from it.
Note that this would work since 2.5.X. In early versions you would need to write a custom deserialiser. Here is an example:
public class JacksonEnumObjectShape {
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final JsonNode jsonNode) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (type.value.equals(jsonNode.get("value").asText())) {
return type;
}
}
return null;
}
}
// can be avoided since 2.5
public static class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(
final JsonParser jp,
final DeserializationContext ctxt) throws IOException {
final JsonNode jsonNode = jp.readValueAsTree();
return ReturnedRateType.fromValue(jsonNode);
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(ReturnedRateType.AA);
System.out.println(json);
System.out.println(mapper.readValue(json, ReturnedRateType.class));
}
}
Output:
{"value":"AA"}
AA

Categories