I would like deserialize my custom serialized objects. My objects are basically consisting a simple Pair implementation.
class School{
Integer id;
String schoolName;
}
class Student{
Integer id;
Integer schoolId;
String studentName;
}
#JsonSerialize(using=PairSerializer.class)
public class Pair<V,K>{
V v;
K k;
}
Here is the result
[
{
"v":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"k":{
"id":3,
"schoolName":"School 3"
}
},
{
"v":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
},
"k":{
"id":3,
"schoolName":"School 3"
}
}
]
v and k as field name in json is pretty ugly. That is why I have written a custom serializer as this:
#Override
public void serialize(Pair pair, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getK().getClass().getSimpleName() ), pair.getK());
jsonGenerator.writeObjectField(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,pair.getV().getClass().getSimpleName() ), pair.getV());
jsonGenerator.writeEndObject();
}
The result is exactly what I want. v and k field names are replaced by their class names.
[
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
}
},
{
"school":{
"id":3,
"schoolName":"School 3"
},
"student":{
"id":2,
"schoolId":3,
"studentName":"C. Koc"
}
}
]
Here is the my question. How can I deserialize my json string to List<Pair<V, K> ? The real problem is that V and K are depends on the deserialized context it might vary as Student, School or another pair implementation.
public class PairDeserializer extends JsonDeserializer<Pair> {
public PairDeserializer() {
}
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// I need to Deserialized generic type information of Pair
}
}
I think, you should create your own PropertyNamingStrategy. For example see my simple implementation:
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
Now you can use it in this way:
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
//etc
Example JSON output:
{ "school" : { "id" : 1,
"schoolName" : "The Best School in the world"
},
"student" : { "id" : 1,
"schoolId" : 1,
"studentName" : "Arnold Shwarz"
}
}
EDIT
Because my answer is not clear for everyone I present full example source code which serialize Java POJO objects into JSON and "vice versa".
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
public class JacksonProgram {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
List<Pair<Student, School>> pairs = createDataForSerialization();
Map<String, String> mapping = createSchoolStudentMapping();
JsonConverter jsonConverter = new JsonConverter(mapping);
String json = jsonConverter.toJson(pairs);
System.out.println("JSON which represents list of pairs:");
System.out.println(json);
List<Pair<Student, School>> value = jsonConverter.fromJson(json, List.class);
System.out.println("----");
System.out.println("Deserialized version:");
System.out.println(value);
}
private static Map<String, String> createSchoolStudentMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "student");
mapping.put("v", "school");
return mapping;
}
private static List<Pair<Student, School>> createDataForSerialization() {
List<Pair<Student, School>> pairs = new ArrayList<Pair<Student, School>>();
pairs.add(new Pair<Student, School>(new Student(1, 3, "O. Bas"), new School(3, "School 3")));
pairs.add(new Pair<Student, School>(new Student(2, 4, "C. Koc"), new School(4, "School 4")));
return pairs;
}
}
class JsonConverter {
private Map<String, String> mapping;
private ObjectMapper objectMapper;
private JsonFactory jsonFactory;
public JsonConverter(Map<String, String> mapping) {
this.mapping = mapping;
initJsonObjects();
}
private void initJsonObjects() {
objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new MapTransformNamingStrategy(mapping));
jsonFactory = new JsonFactory();
}
public String toJson(Object object) throws Exception {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter);
objectMapper.writeValue(jsonGenerator, object);
return stringWriter.toString();
}
public <T> T fromJson(String json, Class<T> expectedType) throws Exception {
JsonParser jsonParser = jsonFactory.createJsonParser(json);
return objectMapper.readValue(jsonParser, expectedType);
}
}
class MapTransformNamingStrategy extends LowerCaseWithUnderscoresStrategy {
private static final long serialVersionUID = 1L;
private Map<String, String> mapping;
public MapTransformNamingStrategy(Map<String, String> mapping) {
this.mapping = mapping;
}
#Override
public String translate(String property) {
if (mapping.containsKey(property)) {
return mapping.get(property);
}
return property;
}
}
class School {
private Integer id;
private String schoolName;
public School() {
}
public School(Integer id, String schoolName) {
this.id = id;
this.schoolName = schoolName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
#Override
public String toString() {
return "School [id=" + id + ", schoolName=" + schoolName + "]";
}
}
class Student {
private Integer id;
private Integer schoolId;
private String studentName;
public Student() {
}
public Student(Integer id, Integer schoolId, String studentName) {
this.id = id;
this.schoolId = schoolId;
this.studentName = studentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
#Override
public String toString() {
return "Student [id=" + id + ", schoolId=" + schoolId + ", studentName=" + studentName
+ "]";
}
}
class Pair<V, K> {
private V v;
private K k;
public Pair() {
}
public Pair(V v, K k) {
this.v = v;
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
#Override
public String toString() {
return "Pair [v=" + v + ", k=" + k + "]";
}
}
The full output log:
JSON which represents list of pairs:
[{"school":{"id":1,"schoolId":3,"studentName":"O. Bas"},"student":{"id":3,"schoolName":"School 3"}},{"school":{"id":2,"schoolId":4,"studentName":"C. Koc"},"student":{"id":4,"schoolName":"School 4"}}]
----
Deserialized version:
[{school={id=1, schoolId=3, studentName=O. Bas}, student={id=3, schoolName=School 3}}, {school={id=2, schoolId=4, studentName=C. Koc}, student={id=4, schoolName=School 4}}]
Because the output JSON is not formatted I present it in more understandable version:
[
{
"school":{
"id":1,
"schoolId":3,
"studentName":"O. Bas"
},
"student":{
"id":3,
"schoolName":"School 3"
}
},
{
"school":{
"id":2,
"schoolId":4,
"studentName":"C. Koc"
},
"student":{
"id":4,
"schoolName":"School 4"
}
}
]
As you can see, we create new JsonConverter object with definition of mapping between Pair property names and which names we want to see in JSON string representation. Now if you have for example Pair<School, Room> you can create mapping Map in this way:
private static Map<String, String> createSchoolRoomMapping() {
Map<String, String> mapping = new HashMap<String, String>();
mapping.put("k", "school");
mapping.put("v", "room");
return mapping;
}
I was going for an answer with some annotation (JsonTypeInfo and JsonUnwrapped), but those two don't work well together apparently (see this issue). That would of handled both the serialization and deserialization part of your problem, without relying on custom de/serializer. Instead, you'll need a custom deserializer, which does something along those line:
class PairDeserializer extends JsonDeserializer<Pair>{
static Map<String, Class> MAPPINGS = new HashMap<String, Class>();
#Override
public Pair deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object key = deserializeField(jp);
Object value = deserializeField(jp);
Pair pair = new Pair();
pair.k = key;
pair.v = value;
jp.nextToken();
return pair;
}
private Object deserializeField(JsonParser jp) throws IOException, JsonParseException, JsonProcessingException {
jp.nextValue();
String className = jp.getCurrentName();
return jp.readValueAs(MAPPINGS.get(className));
}
}
Then you only need to register the mappings you need
Related
I have a JSON looks like the following:
{
"name": "John",
"age": 20,
"skills": [
]
}
the skills if it's not empty looks like the following:
{
"skills": [
"skill_1": {
},
"skill_2": {
}]
}
and I need to deserialize this JSON to POJO:
public class Profile {
public String name;
public int age;
#JsonDeserialize(using = SkillsMapDeserializer.class)
public Map<String, Skill> skills;
}
public class Skill {
public String skillName;
public int age;
}
and my SkillsMapDeserializer looks like the following:
public class SkillsMapDeserializer extends JsonDeserializer<Map<String, Skill>> {
#Override
public Map<String, Skill> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
final Map<String, Skill> map = jsonParser.readValueAs(new TypeReference<Map<String, Skill>>() {
});
if (map == null) {
return new HashMap<>();
}
return map;
}
}
if the skills aren't empty all works fine, but if the skills are an empty array I get an exception that looks like the following:
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap<java.lang.Object,java.lang.Object>` out of START_ARRAY token
How can I fix this issue?
From your json data, it seem skills is an array of object.
"skills": [],
"skills": [
"skill_1": {},
"skill_2": {}
]
But your java define it as Map
public Map<String, Skill> skills;
That's why you got an exception when trying convert array to map directly.
If you can't change the POJOs Profile, you should have an mediate step to convert list to Map.
public class SkillsMapDeserializer extends JsonDeserializer<Map<String, Skill>> {
#Override
public Map<String, Skill> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
final List<Map<String,Skill>> skills = jsonParser.readValueAs(new TypeReference<List<Map<String,Skill>>>>() {
});
return functionConvertListToMapWithParam(skills);
}
}
skills is not a map. it should be list of objects. try to modify your POJO like below:-
public class Profile {
#JsonProperty("name")
private String name;
#JsonProperty("age")
private Integer age;
#JsonProperty("skills")
private List < Object > skills = null;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
#JsonProperty("age")
public Integer getAge() {
return age;
}
#JsonProperty("age")
public void setAge(Integer age) {
this.age = age;
}
#JsonProperty("skills")
public List < Object > getSkills() {
return skills;
}
#JsonProperty("skills")
public void setSkills(List < Object > skills) {
this.skills = skills;
}
}
I have the following POJO that can be serialized into bytes or json.
public final class Message {
private final Data data;
private final Request request;
private final Response response;
public Message() {
this.data = new Data();
this.request = new Request();
this.response = new Response();
}
public Data getData() {
return data;
}
public Request getRequest() {
return request;
}
public Response getResponse() {
return response;
}
public Object query(String pointer) {
return toJson().query(pointer);
}
public byte[] toBytes() {
try {
return new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(this);
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public JSONObject toJson() {
try {
return new JSONObject(new ObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
#Override
public String toString() {
try {
return toString(0);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
public String toString(int indent) {
try {
return toJson().toString(indent);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
}
Reference Classes:
public class Data {
private final Map<String, Map<String, Object>> dataMap;
public Data() {
this.dataMap = new HashMap();
}
public Data addToSet(String name, String key, Object value) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
map = new HashMap();
}
map.put(key, value);
dataMap.put(name, map);
return this;
}
public Map<String, Map<String, Object>> getSets() {
return dataMap;
}
public Data updateSet(String name, String key, Object value) {
return Data.this.addToSet(name, key, value);
}
public Data removeFromSet(String name, String key) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
throw new MessageException("No such property '" + key + "' for set '" + name + "'");
}
map.remove(key);
return this;
}
public Map<String, Object> getSet(String name) {
return dataMap.get(name);
}
}
public class Request {
private String method;
private String resource;
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return Objects.toString(method, "");
}
public String getResource() {
return Objects.toString(resource, "");
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(String payload) {
try {
this.body.putAll(new ObjectMapper().readValue(payload, new TypeReference<Map<String, Object>>() {
}));
return this;
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
}
public class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public Response() {
}
public String getCode() {
return Objects.toString(code, "");
}
public String getData() {
return Objects.toString(data, "");
}
public String getMessageId() {
return Objects.toString(messageId, "");
}
public String getTimestamp() {
return Objects.toString(timestamp, "");
}
public String getDescription() {
return Objects.toString(description, "");
}
public Response setCode(String code) {
this.code = code;
return this;
}
public Response setData(String data) {
this.data = data;
return this;
}
public Response setMessageId(String messageId) {
this.messageId = messageId;
return this;
}
public Response setTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
public Response setDescription(String description) {
this.description = description;
return this;
}
}
When serializing to json I get a valid string
{
"request": {
"headers": {},
"method": "",
"resource": "",
"body": {
"whatsapp": {
"conversationId": "39f09c41-1bd3-4e81-b829-babed3747d4b",
"name": "Dave",
"source": "+123456789098"
},
"payment": {
"product": "chocolate",
"amount": 1,
"method": "cashapp",
"msisdn": "123456789098",
"entity": "The Fudge Shop"
}
},
"parameters": {}
},
"data": {
"sets": {
"whatsapp": {
"provider": "clickatell",
"name": "Dave",
"destination": "123456789098",
"source": "123456789098",
"message": "Your payment of $1.00 received, your receipt.no is QWJ124XPA9."
},
"cashapp": {
"amount": 1,
"receiptNo": "QWJ124XPA9",
"name": "Dave Chapelle",
"msisdn": "123456789098"
}
}
},
"response": {
"code": "202",
"data": "",
"messageId": "20210623160202a647d32ee9ae477f9c90d8b1fbfd763a",
"description": "Processing Request",
"timestamp": "2021-06-23 16:02:02.408"
}
}
When I attempt to deserialize the json back to a pojo
Message output = new ObjectMapper().readValue(json.toString(), Message.class);
I get the error :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
The error seems to be generated from the Request class when attempting to deserialize the Map<String, Object> body:
How may I deserialize the Map correctly?
For the String-Problem, these sources might help:
Can not deserialize instance of java.lang.String out of START_OBJECT token
https://www.baeldung.com/jackson-map#1-mapltstring-stringgt-deserialization
Why this code can't work
Jackson is not much more powerful than you are.
If Jackson gets an object to serialize, it tries to serialize all of its values. And only its values (which is pretty good for the independence from classes). This is a json object:
{
"type":"apple",
"quantity":3,
"imageID":17
}
Now, what is the class of this object? It could be Fruit.class, Image.class or even RoundObject.class, json doesn't know and Jackson neither.
So how does json find out what the class is? By looking at the type of the object reference. In your case, it's Object. In Object.class, Jackson cannot find a constructor that requires the variables of the object that has been saved, so it crashes.
Solution
Trying to serialize objects is not a good idea. If you have very different classes you want to put in, e.g. Apple and Banana, make an interface or abstract class called Fruit that both of them implement. Now, use this annotation at the top of this class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type") // name of the variable to save the kind of object you put in. NO VARIABLES in all classes that extend from Fruit are allowed to have this name (or at least #JsonProperty).
#JsonSubTypes({
#JsonSubTypes.Type(value = Apple.class, name = "banana"),
#JsonSubTypes.Type(value = Banana.class, name = "apple"),
})
And using a Map<String, Fruit> should work.
The solution that worked for me was using custom deserialization, #JsonDeserialize annotation & JsonDeserializer interface, in order to achieve the desired results.
Below is the solution:
public class Request {
private String method;
private String resource;
#JsonDeserialize(using = BodyDeserializer.class)
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return method;
}
public String getResource() {
return resource;
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(Map<String, Object> body) {
this.body.putAll(body);
return this;
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
private static class BodyDeserializer extends JsonDeserializer<Map<String, Object>> {
#Override
public Map<String, Object> deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonDeserializer<Object> deserializer = dc.findRootValueDeserializer(dc.constructType(Map.class));
Map<String, Object> map = (Map<String, Object>) deserializer.deserialize(jp, dc);
return map;
}
}
}
Try this one JacksonUtils
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
This is complete snippet:
public class MavenMain {
public static void main(String... args) {
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
}
private static Message createMessage() {
Message message = new Message();
message.setData(createData());
message.setRequest(createRequest());
message.setResponse(createResponse());
return message;
}
private static Data createData() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("provider", "clickatell");
whatsapp.put("name", "Dave");
whatsapp.put("destination", "123456789098");
whatsapp.put("source", "123456789098");
whatsapp.put("message", "Your payment of $1.00 received, your receipt.no is QWJ124XPA9.");
Map<String, Object> cashapp = new LinkedHashMap<>();
cashapp.put("receiptNo", "QWJ124XPA9");
cashapp.put("name", "Dave Chapelle");
cashapp.put("msisdn", "123456789098");
Map<String, Map<String, Object>> dataMap = new LinkedHashMap<>();
dataMap.put("whatsapp", whatsapp);
dataMap.put("cashapp", cashapp);
Data data = new Data();
data.setDataMap(dataMap);
return data;
}
private static Request createRequest() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("conversationId", "39f09c41-1bd3-4e81-b829-babed3747d4b");
whatsapp.put("name", "Dave");
whatsapp.put("source", "+123456789098");
Map<String, Object> payment = new LinkedHashMap<>();
payment.put("product", "chocolate");
payment.put("amount", 1);
payment.put("method", "cashapp");
payment.put("msisdn", "123456789098");
payment.put("entity", "The Fudge Shop");
Map<String, Object> body = new HashMap<>();
body.put("whatsapp", whatsapp);
body.put("payment", payment);
Request request = new Request();
request.setHeaders(Collections.emptyMap());
request.setMethod("");
request.setResource("");
request.setBody(body);
request.setParameters(Collections.emptyMap());
return request;
}
private static Response createResponse() {
Response response = new Response();
response.setCode("202");
response.setData("");
response.setMessageId("20210623160202a647d32ee9ae477f9c90d8b1fbfd763a");
response.setDescription("Processing Request");
response.setTimestamp("2021-06-23T16:02:02.408");
return response;
}
}
class Message {
private Data data;
private Request request;
private Response response;
public void setData(Data data) {
this.data = data;
}
public void setRequest(Request request) {
this.request = request;
}
public void setResponse(Response response) {
this.response = response;
}
}
class Data {
#JsonProperty("sets")
private Map<String, Map<String, Object>> dataMap;
public void setDataMap(Map<String, Map<String, Object>> dataMap) {
this.dataMap = dataMap;
}
}
class Request {
private String method;
private String resource;
private Map<String, Object> body;
private Map<String, String> headers;
private Map<String, String[]> parameters;
public void setMethod(String method) {
this.method = method;
}
public void setResource(String resource) {
this.resource = resource;
}
public void setBody(Map<String, Object> body) {
this.body = body;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void setParameters(Map<String, String[]> parameters) {
this.parameters = parameters;
}
}
class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public void setCode(String code) {
this.code = code;
}
public void setData(String data) {
this.data = data;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public void setDescription(String description) {
this.description = description;
}
}
In case you want to use immutable object, then it's a bit another configuration of models, but code in the main class will be the same.
I have two classes:
public class ResponseInfo {
private final int code;
private final String description;
#JsonCreator
public static ResponseInfo of(
#JsonProperty("code") int code,
#JsonProperty("description") String description
) {
return new ResponseInfo(code, description);
}
private ResponseInfo(
int code,
String description
) {
this.code = code;
this.description = description;
}
#JsonProperty("code")
public int code() {
return code;
}
#JsonProperty("description")
public String description() {
return description;
}
}
and:
public class Response<T> {
private final ResponseInfo responseInfo;
private final T payload;
public static <T> Response<T> of(ResponseInfo responseInfo, T payload) {
return new Response<>(responseInfo, payload);
}
private Response(ResponseInfo responseInfo, T payload) {
this.responseInfo = responseInfo;
this.payload = payload;
}
#JsonUnwrapped
public ResponseInfo responseInfo() {
return responseInfo;
}
#JsonUnwrapped
public T payload() {
return payload;
}
}
I use them to add additional info into response (as code and description). For example:
Response.of(ResponseInfo.of(0, "OK"), User.of("Oleg", 23))
will be serialized into:
{
"age": 23,
"code": 0,
"description": "OK",
"name": "Oleg"
}
How the deserialization of Response can be done?
I can't use #JsonProperty in #JsonCreator directly cause I don't know properties of payload.
JsonCreator with #JsonUnwrapped also doesn't work.
I am using jackson-datatype-jdk8:2.9.5.
I've created implementation.
public class ResponseDeserializer
extends JsonDeserializer<Response<?>>
implements ContextualDeserializer {
private JavaType type;
#SuppressWarnings("unused")
public ResponseDeserializer() {
}
private ResponseDeserializer(JavaType type) {
this.type = type;
}
#Override
public JsonDeserializer<?> createContextual(
DeserializationContext context,
BeanProperty beanProperty
) {
JavaType contextualType = context.getContextualType();
if(contextualType == null) {
contextualType = beanProperty.getMember()
.getType();
}
if (!contextualType.isTypeOrSubTypeOf(Response.class)) {
throw new IllegalArgumentException("contextualType should be " + Response.class.getName());
}
final JavaType payloadType = contextualType.containedType(0);
return new ResponseDeserializer(payloadType);
}
#Override
public Response<?> deserialize(
JsonParser jsonParser,
DeserializationContext context
) throws IOException {
final ObjectCodec codec = jsonParser.getCodec();
JsonNode rootNode = codec.readTree(jsonParser);
final ResponseInfo responseInfo = ResponseInfo.of(
rootNode.get("code").asInt(),
rootNode.get("description").asText()
);
final JsonNode payloadNode = createPayloadNode(rootNode, codec);
final JsonParser payloadParser = payloadNode.traverse();
final Object payload = codec.readValue(payloadParser, type);
return Response.of(responseInfo, payload);
}
private JsonNode createPayloadNode(JsonNode rootNode, ObjectCodec codec) {
final Map<String, JsonNode> remainingNodes = findRemainingNodes(rootNode);
if(remainingNodes.size() == 1) {
final JsonNode payloadNode = remainingNodes.get("payload");
if(payloadNode != null && !payloadNode.isObject()) {
return payloadNode;
}
}
return buildRemainingNode(remainingNodes, codec);
}
private JsonNode buildRemainingNode(Map<String, JsonNode> remainingNodes, ObjectCodec codec) {
final ObjectNode remainingNode = (ObjectNode) codec.createObjectNode();
remainingNodes.forEach(remainingNode::set);
return remainingNode;
}
private Map<String, JsonNode> findRemainingNodes(JsonNode rootNode) {
Map<String, JsonNode> remainingNodes = new HashMap<>();
rootNode.fields()
.forEachRemaining(entry -> {
final String key = entry.getKey();
if(key.equals("code") || key.equals("description")) {
return;
}
remainingNodes.put(key, entry.getValue());
});
return remainingNodes;
}
}
I have gone through the threads from SOF which talks about getting nested JSON using GSON. Link 1 Link 2. My JSON file is as shown below
{
"Employee_1": {
"ZipCode": 560072,
"Age": 50,
"Place": "Hawaii",
"isDeveloper": true,
"Name": "Mary"
},
"Employee_2": {
"ZipCode": 560072,
"Age": 80,
"Place": "Texas",
"isDeveloper": true,
"Name": "Jon"
}
}
my classes are as shown below
public class Staff {
String Employee_1 ;
}
class addnlInfo{
String Name;
String Place;
int Age;
int Zipcode;
boolean isDeveloper;
}
The deserializer class which I built is as shown below
class MyDeserializer implements JsonDeserializer<addnlInfo>{
public addnlInfo deserialize1(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
// Get the "content" element from the parsed JSON
JsonElement content = je.getAsJsonObject().get("Employee_1");
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return new Gson().fromJson(content, addnlInfo.class);
}
#Override
public TokenMetaInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// TODO Auto-generated method stub
return null;
}
The main file
Gson gson = new GsonBuilder()
.registerTypeAdapter(addnlInfo.class, new MyDeserializer())
.create();
String jsonObject= gson.toJson(parserJSON);
addnlInfo info= gson.fromJson(jsonObject, addnlInfo .class);
System.out.println(info.Age + "\n" + info.isDeveloper + "\n" + info.Name + "\n" + info.Place);
Staff parentNode = gson.fromJson(jsonObject, Staff.class);
System.out.println(parentNode.Employee_1);
The problem:
My Subparent element (e.g. 'Employee_1') keeps changing. Do I have to construct multiple deserializers?
Also, I get "Expected a string but was BEGIN_OBJECT" which I understand as we use nestedJSON.
I am not sure how your classes translate to your JSON, but you are making this too complex.
I renamed fields and class names to adhere to Java standards.
Main.java
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class Main {
public static void main(String[] args) {
Map<String, Staff> employees = new LinkedHashMap<String, Staff>();
employees.put("Employee_1", new Staff(new Info("Mary", "Hawaii", 50, 56072, true)));
employees.put("Employee_2", new Staff(new Info("Jon", "Texas", 80, 56072, true)));
String jsonString = new GsonBuilder().setPrettyPrinting().create().toJson(employees);
System.out.println("# SERIALIZED DATA:");
System.out.println(jsonString);
Type mapOfStaff = new TypeToken<Map<String, Staff>>() {}.getType();
Map<String, Staff> jsonObject = new Gson().fromJson(jsonString, mapOfStaff);
System.out.println("\n# DESERIALIZED DATA:");
for (Entry<String, Staff> entry : jsonObject.entrySet()) {
System.out.printf("%s => %s%n", entry.getKey(), entry.getValue());
}
}
}
Staff.java
public class Staff {
private Info info;
public Staff(Info info) {
this.info = info;
}
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
#Override
public String toString() {
return String.format("Staff [info=%s]", info);
}
}
Info.java
public class Info {
private String name;
private String place;
private int age;
private int zipcode;
private boolean developer;
public Info(String name, String place, int age, int zipcode, boolean developer) {
this.name = name;
this.place = place;
this.age = age;
this.zipcode = zipcode;
this.developer = developer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getZipcode() {
return zipcode;
}
public void setZipcode(int zipcode) {
this.zipcode = zipcode;
}
public boolean isDeveloper() {
return developer;
}
public void setDeveloper(boolean developer) {
this.developer = developer;
}
#Override
public String toString() {
return String.format(
"Info [name=%s, place=%s, age=%d, zipcode=%d, developer=%b]",
name, place, age, zipcode, developer
);
}
}
Output
# SERIALIZED DATA:
{
"Employee_1": {
"info": {
"name": "Mary",
"place": "Hawaii",
"age": 50,
"zipcode": 56072,
"developer": true
}
},
"Employee_2": {
"info": {
"name": "Jon",
"place": "Texas",
"age": 80,
"zipcode": 56072,
"developer": true
}
}
}
# DESERIALIZED DATA:
Employee_1 => Staff [info=Info [name=Mary, place=Hawaii, age=50, zipcode=56072, developer=true]]
Employee_2 => Staff [info=Info [name=Jon, place=Texas, age=80, zipcode=56072, developer=true]]
I have following classes:
public class MyProperty
{
public String Key;
public String Value;
}
public class MyModel
{
public String Name;
public List<MyProperty> Properties;
}
When I try to serialize an object of type MyObject like this:
MyModel m = new MyModel(){{
Name="aaaa";
Properties = new ArrayList<MyProperty>();
}};
m.Properties = new ArrayList<MyProperty>();
m.Properties.add(new MyProperty() {{ Key="a"; Value="1"; }});
m.Properties.add(new MyProperty() {{ Key="b"; Value="11"; }});
m.Properties.add(new MyProperty() {{ Key="c"; Value="111"; }});
String json1 = g.toJson(m, MyModel.class);
I'm getting following result:
{"Name":"aaaa","Properties":[null,null,null]}
Why is the list of properties serialized to list of null's when the source objects are definitely not null?
Deserialization of a string
{"Name":"aaaa","Properties":[{"Key":"a","Value":"1" etc }]}
works fine.
The problem you're likely hitting is polymorphism - you're model says that the Properties are of type "MyProperty" but your code fragment uses "SyncProperty". There are some gotchas to doing this with Gson - have a look at the discussion here: How to handle deserializing with polymorphism?
I will tell you why this code has the output you are looking for and not the code you have in question.
Code
import java.util.ArrayList;
import com.google.gson.Gson;
public class TestOneDrive {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyModel model = new MyModel();
model.setName("someName");
ArrayList<MyProperty> myProperties = new ArrayList<>();
for (int i = 0; i < 5; i++) {
MyProperty myProperty = new MyProperty();
myProperty.setKey("Key_" + i);
myProperty.setValue("Value_" + i);
myProperties.add(myProperty);
}
model.setProperties(myProperties);
String result = (new Gson()).toJson(model);
System.out.println("" + result);
}
}
class MyProperty {
public String Key;
public String Value;
public String getKey() {
return Key;
}
public void setKey(String key) {
Key = key;
}
public String getValue() {
return Value;
}
public void setValue(String value) {
Value = value;
}
}
class MyModel {
public String Name;
public ArrayList<MyProperty> Properties;
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public ArrayList<MyProperty> getProperties() {
return Properties;
}
public void setProperties(ArrayList<MyProperty> properties) {
Properties = properties;
}
}
Output
{
"Name": "someName",
"Properties": [
{
"Key": "Key_0",
"Value": "Value_0"
},
{
"Key": "Key_1",
"Value": "Value_1"
},
{
"Key": "Key_2",
"Value": "Value_2"
},
{
"Key": "Key_3",
"Value": "Value_3"
},
{
"Key": "Key_4",
"Value": "Value_4"
}
]
}