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 the APIResponse class which extends the Object class like <T extends Object> but while getting the response body from the rest template getting the data into the Object class, not to the Book class.
If I try to fetch the data into the Book class it gives the null.
I have tried typecasting the response in The Book Object but no success.
ex.ApiResponse<Book>.
public void testCreate(){
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
BookDto bookDto = new BookDto("Pranav","dummy","dummy");
String url = "http://localhost:9090/books";
HttpEntity<BookDto> httpEntity = getHttpEntity(bookDto);
ResponseEntity<Object> book = restTemplate.exchange(url,HttpMethod.POST,httpEntity,Object.class);
//Able to Get the response body in Object but if I try to change it to the Book the response body is coming null.
}
private HttpEntity<BookDto> getHttpEntity(BookDto bookDto) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Accept",MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity<>(bookDto,headers);
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class APIResponse<T extends Object> implements Serializable {
/**
* status & message fields have not setter. They are assigned value when
* initial by APIStatus parameter
*/
private int status;
private String message;
private T data;
public APIResponse(APIStatus apiStatus, T data) {
if (apiStatus == null) {
throw new IllegalArgumentException("APIStatus must not be null");
}
this.status = apiStatus.getCode();
this.message = apiStatus.getDescription();
this.data = data;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public enum APIStatus {
// Common status
OK(200, null);
private final int code;
private final String description;
private APIStatus(int s, String v) {
code = s;
description = v;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
#Component
public class ResponseUtil {
private APIResponse<Book> createResponse(APIStatus apiStatus, Object data) {
return new APIResponse(apiStatus, data);
}
// base method
public ResponseEntity<APIResponse<Book>> buildResponse(APIStatus apiStatus, Object data, HttpStatus httpStatus) {
return new ResponseEntity(createResponse(apiStatus, data), httpStatus);
}
public ResponseEntity<APIResponse<Book>> successResponse(Object data) {
return buildResponse(APIStatus.OK, data, HttpStatus.OK);
}
}
#RestController
public class BookController {
#Autowired
BookService bookService;
#Autowired
protected ResponseUtil responseUtil;
#GetMapping("/books")
ResponseEntity<APIResponse<Book>> read(){
return responseUtil.successResponse(bookService.findAll());
}
}
I expect the response body result into the Book Object, not to the General Object class.
I have a generic Java Message object that's represented by the following json string:
{
"type": "Example",
"processTime": 3.4,
"payload":
{
"id": "someString",
"type": "anotherString",
"message": "yetAnotherString"
}
}
The Java Message object is generic. I also have an object called Event. When trying to convert the json into a Message<Event> object using gson, a Message object is returned with the correct json values, but the nested generic object is somehow returned as a "LinkedTreeMap" object instead of an Event object. I know this has something to do with type erasure, but I still can't seem to figure out how to return a Message<Event> from the json.
This is my main():
public class App {
public static void main(String[] args) {
//The json string to convert into a "Message<Event>" object
String jsonString = "{\"type\":\"Example\",\"processTime\":3.4,\"payload\":{\"id\":\"someString\",\"type\":\"anotherString\",\"message\":\"yetAnotherString\"}}";
Message<Event> message = new Message<Event>();
message = message.convertJsonToObject(jsonString, Event.class);
System.out.println(message.getClass().getName()); //returns as a "Message" class -- as expected
System.out.println(message.getPayload().getClass().getName()); //returns as a "LinkedTreeMap" instead of an "Event" object
}
}
Message class:
public class Message<T> {
private String type;
private double processTime;
private T payload;
public Message(String type, double processTime, T payload) {
this.type = type;
this.processTime = processTime;
this.payload = payload;
}
public Message() {
type = null;
processTime = 0;
payload = null;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public double getProcessTime() {
return processTime;
}
public void setProcessTime(double processTime) {
this.processTime = processTime;
}
public T getPayload() {
return payload;
}
public void setPayload(T payload) {
this.payload = payload;
}
public Message<T> convertJsonToObject(String jsonString, Class<T> classType) {
GsonBuilder gson = new GsonBuilder();
Type collectionType = new TypeToken<Message<T>>() {}.getType();
Message<T> myMessage = gson.create().fromJson(jsonString, collectionType);
return myMessage;
}
#Override
public String toString() {
return new Gson().toJson(this);
}
}
Event class:
public class Event {
private String id;
private String type;
private String message;
public Event(String id, String type, String message) {
this.id = id;
this.type = type;
this.message = message;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public String toString() {
return new Gson().toJson(this);
}
}
I want to parse this following dynamic JSON
{
"lowfares": {
"2017-07-30": {
"price": "1208.00",
"tax": "946.00",
"totalprice": "2154.00"
},
"2017-07-31": {
"price": "1208.00",
"tax": "946.00",
"totalprice": "2154.00"
}
}
}
This is my class contains price, tax, and totalprice
public class PriceModel {
#SerializedName("price")
private String price;
#SerializedName("tax")
private String tax;
#SerializedName("totalprice")
private String totalprice;
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getTax() {
return tax;
}
public void setTax(String tax) {
this.tax = tax;
}
public String getTotalPrice() {
return totalprice;
}
public void setTotalPrice(String totalPrice) {
this.totalprice = totalPrice;
}
}
This is my class to contain hashmap to store the response
public class ResponseModel {
#SerializedName("prices")
#Expose
private Map<String,PriceModel> priceModelMap;
public Map<String, PriceModel> getPriceModelMap() {
return priceModelMap;
}
public void setPriceModelMap(Map<String, PriceModel> priceModelMap) {
this.priceModelMap = priceModelMap;
}
}
in API interface, this is how I get the response
#GET("getprice/{start}/{end}/1/2")
Call<ResponseModel> getResponse(#Path("start") String start, #Path("end") String end);
and in MainActivity, I execute like this
Call call = apiInterface.getResponse("CRB","IMY");
call.enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
Log.d("TAG",response.code()+" ");
Log.d("TAG","REsponse: "+response.body());
ResponseModel responseModel = (ResponseModel) response.body();
Log.d("TAG","REsponse: "+responseModel.getPriceModelMap());
Map<String, PriceModel> priceModelMap = responseModel.getPriceModelMap();
for (Map.Entry<String,PriceModel> entry : priceModelMap.entrySet()){
String key = entry.getKey();
PriceModel priceModel = entry.getValue();
System.out.println("KEY: "+key+" value: "+priceModel.getPrice());
}
}
#Override
public void onFailure(Call call, Throwable t) {
call.cancel();
}
});
I want to get price, tax, totalprice. But using my method, I tried getPrice method give null value.
How can I get the date and the values from that JSON? Thanks
So in the end I decided not to use retrofit as I couldnt find a way to parse the json as I wanted.
What I did to parse that dynamic json response
private HashMap<String,JSONObject> getLowfaresJson(JSONObject data){
HashMap<String,JSONObject> result = new HashMap<>();
try {
JSONObject lowfareJson = data.getJSONObject("lowfares");
Iterator keys = lowfareJson.keys();
while ((keys.hasNext())){
//Getting dynamic key from json
String currentDynamicKey = (String) keys.next();
//Getting dynamic value from json
JSONObject currentDynamicValue = lowfareJson.getJSONObject(currentDynamicKey);
result.put(currentDynamicKey,currentDynamicValue);
}
} catch (JSONException e) {
e.printStackTrace();
}
return result;
}
that method will return hashmap from dynamic json response. Hope this will help someone
You can simply gson.
Import in your project.
dependencies {
compile 'com.google.code.gson:gson:2.8.1'
}
public class TestModel {
private String name;
private int age;
private String position;
}
Use:
String strModel ="Staff{name='john', age=35, position='Developer'}"
Gson gson = new Gson();
TestModel testModel = gson.fromJson(strModel, TestModel .class);
Read more:Samples
I intended to bind a JSON string to POJO annotated with GSON, the JSON response is from the ReSTFUL service to list all countries: http://services.groupkt.com/country/get/all
the response is fine, which looks like
{
"RestResponse": {
"messages": [
"More webservices are available at http://www.groupkt.com/post/f2129b88/services.htm",
"Total [249] records found."
],
"result": [
{
"name": "Afghanistan",
"alpha2_code": "AF",
"alpha3_code": "AFG"
},
{
"name": "Ă…land Islands",
"alpha2_code": "AX",
"alpha3_code": "ALA"
},
...
]
}
}
The POJO Country and its associated classes were created using this tool:http://www.jsonschema2pojo.org/ and they look like:
Country.java
public class Country implements Serializable{
#SerializedName("RestResponse")
#Expose
private RestResponse restResponse;
public RestResponse getRestResponse() {
return restResponse;
}
public void setRestResponse(RestResponse restResponse) {
this.restResponse = restResponse;
}
}
RestResponse.java
public class RestResponse implements Serializable{
#SerializedName("messages")
#Expose
private List<String> messages = null;
#SerializedName("result")
#Expose
private List<Result> result = null;
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
public List<Result> getResult() {
return result;
}
public void setResult(List<Result> result) {
this.result = result;
}
}
Result.java
public class Result implements Serializable{
#SerializedName("name")
#Expose
private String name;
#SerializedName("alpha2_code")
#Expose
private String alpha2Code;
#SerializedName("alpha3_code")
#Expose
private String alpha3Code;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlpha2Code() {
return alpha2Code;
}
public void setAlpha2Code(String alpha2Code) {
this.alpha2Code = alpha2Code;
}
public String getAlpha3Code() {
return alpha3Code;
}
public void setAlpha3Code(String alpha3Code) {
this.alpha3Code = alpha3Code;
}
}
The code below however failed to bind the JSON string to the GSON annotated POJOs - the restResponse is NULL, so are the message and result. Can anyone tell me what went wrong?
#SpringBootApplication
public class App implements CommandLineRunner
{
private static Logger log = LoggerFactory.getLogger(App.class);
/*
* boiler plate code
* */
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
/*
* Configuration section
* */
#Bean
public RestTemplate newRestTemplate(){
RestTemplate rt = new RestTemplate();
return rt;
}
/*
* public APIs section
* */
#Autowired
private RestTemplate restTemplate;
#Override
public void run(String... args) throws Exception {
String url = "http://services.groupkt.com/country/get/all";
ResponseEntity<String> res = restTemplate.getForEntity(url, String.class);
log.info("{}",res.getBody());
GsonHttpMessageConverter msgConverter = new GsonHttpMessageConverter();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
msgConverter.setGson(gson);
restTemplate.getMessageConverters().add(msgConverter);
Country country = restTemplate.getForObject(url, Country.class);
RestResponse resp = country.getRestResponse();
List<Result> l = resp.getResult();
for(Result r : l){
log.info("country name = {}",r.getName());
}
}
}
I managed to update the code like below and it works now:
RestTemplate rt = new RestTemplate();
String url = "http://services.groupkt.com/country/get/all";
ResponseEntity<String> resp = rt.getForEntity(url, String.class);
assertEquals(resp.getStatusCode(), HttpStatus.OK);
Gson gson = new GsonBuilder().create();
Country c = gson.fromJson(resp.getBody(), Country.class);
still don't know why the code below didn't work, though.
Country country = restTemplate.getForObject(url, Country.class);