Reputation: 426
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?
Upvotes: 3
Views: 9363
Reputation: 426
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;
}
}
}
Upvotes: 2
Reputation: 18245
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.
Upvotes: 0
Reputation: 1058
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.
Upvotes: 1