I am struggling with deserialising a JSON object wrapped by a generic result structure:
My JSON from the server:
{
"totalsize": 5,
"data" : [
{"name":1},
{"name":2}
]
}
My Java objects from the result:
public class ResponseTo<T> {
public Long totalsize = null;
public final List<T> data = new ArrayList<>();
}
And in this case (could be anything else on another GET request)
public class Item {
public int name;
}
In this case the T is an Item.
How to deserialise a JSON payload into this object structure with:
GSON and/or com.fasterxml.jackson?
I want to have a static method like:
public static <T> ResponseTo<T> stringToObject(String jsonString, Class<T> clazz ) {
final Gson gson = new Gson();
// do some typeadapter function magic
return gson.fromJson( jsonString, ResponseTo.class );
}
which is invoked by
ResponseTo<Item> responseTo = stringToObject( <json-string>, Item.class );
I only receive com.google.gson.internal.LinkedTreeMap as data objects.
What can I do to make it work? - Am I on a wrong way of doing it?
You need to use a type-token to handle generic cases, because the type information for T will be erased by the Java compiler and Gson wouldn't know what type to restore T to.
Type typeToken = new TypeToken<ResponseTo<Item>>() { }.getType();
ResponseTo<Item> responseTo = stringToObject( <json-string>, typeToken );
Gson
You can use internal com.google.gson.internal.$Gson$Types class to implement this:
import com.google.gson.Gson;
import com.google.gson.internal.$Gson$Types;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
public class GsonApp {
public static void main(String[] args) {
String json = "{\n" +
" \"totalsize\": 5,\n" +
" \"data\": [\n" +
" {\n" +
" \"name\": 1\n" +
" },\n" +
" {\n" +
" \"name\": 2\n" +
" }\n" +
" ]\n" +
"}\n";
System.out.println(stringToObject(json, Item.class));
}
static <T> ResponseTo<T> stringToObject(String jsonString, Class<T> clazz) {
final Gson gson = new Gson();
// do some typeadapter function magic
ParameterizedType parameterizedType = $Gson$Types.newParameterizedTypeWithOwner(ResponseTo.class, ResponseTo.class, clazz);
return gson.fromJson(jsonString, parameterizedType);
}
}
class ResponseTo<T> {
public Long totalsize = null;
public final List<T> data = new ArrayList<>();
#Override
public String toString() {
return "ResponseTo{" +
"totalsize=" + totalsize +
", data=" + data +
'}';
}
}
class Item {
public int name;
#Override
public String toString() {
return "Item{" +
"name=" + name +
'}';
}
}
Above code prints:
ResponseTo{totalsize=5, data=[Item{name=1}, Item{name=2}]}
Jackson
In Jackson you can do similar method:
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JsonApp {
public static void main(String[] args) throws Exception {
String json = "{\n" +
" \"totalsize\": 5,\n" +
" \"data\": [\n" +
" {\n" +
" \"name\": 1\n" +
" },\n" +
" {\n" +
" \"name\": 2\n" +
" }\n" +
" ]\n" +
"}\n";
System.out.println(stringToObject(json, Item.class));
}
static <T> ResponseTo<T> stringToObject(String jsonString, Class<T> clazz) throws IOException {
ObjectMapper mapper = new ObjectMapper();
// do some typeadapter function magic
JavaType responseType = mapper.getTypeFactory().constructParametricType(ResponseTo.class, clazz);
return mapper.readValue(jsonString, responseType);
}
}
If you have a type discriminator in the object (i.e. a common field with a unique value in each example) then Jackson #JsonTypeInfo(JsonTypeInfo.As.EXISTING_PROPERTY...) will allow you to define where to look and initialisation can pre-register all possible classes with ObjectMapper.registerSubtypes(Item.class). Jackson will then pick the correct one.
If the rule for which type to instantiate is anything more complex than "look in field X" then you must write your own #JsonTypeResolver to implement said logic (non-trivial).
Related
I love how easy it is to map JSON data to a Java object with Jsonb, but I seem to have stumbled upon a not well-documented use-case...
Given this json data:
{
"id": "test",
"points": [
[
-24.787439346313477,
5.5551919937133789
],
[
-23.788913726806641,
6.7245755195617676
],
[
-22.257251739501953,
7.2461895942687988
]
]
}
What can be used as the object type to store the points-values?
import jakarta.json.bind.annotation.JsonbProperty;
public class Temp {
#JsonbProperty("id")
private String id;
#JsonbProperty("points")
private ??? points;
// Getters-Setters
}
So I can create the Temp-object with:
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
Jsonb jsonb = JsonbBuilder.create();
Temp temp = jsonb.fromJson(jsonString, Temp.class);
So far I've tried the following:
List<Point> --> "Can't deserialize JSON array into: class java.awt.Point"
List<Point2D> --> "Can't deserialize JSON array into: class java.awt.Point2D"
Let's try it:
#Data
public class Temp {
#JsonbProperty("id")
private String id;
#JsonbProperty("points")
private List<List<BigDecimal>> points;
public static void main(String[] args) {
String jsonString = "{\n" +
" \"id\": \"test\",\n" +
" \"points\": [\n" +
" [\n" +
" -24.787439346313477,\n" +
" 5.5551919937133789\n" +
" ],\n" +
" [\n" +
" -23.788913726806641,\n" +
" 6.7245755195617676\n" +
" ],\n" +
" [\n" +
" -22.257251739501953,\n" +
" 7.2461895942687988\n" +
" ]\n" +
" ]\n" +
"}";
Jsonb jsonb = JsonbBuilder.create();
Temp temp = jsonb.fromJson(jsonString, Temp.class);
System.out.println(temp);
}
}
To figure out the default mapping, use a non-generic field and observe it with the debugger:
public class Test {
public static void main(String[] args) {
String json = "{\"id\":\"test\",\"points\":[[-24.787439346313477,5.555191993713379],[-23.78891372680664,6.724575519561768],[-22.257251739501953,7.246189594268799]]}";
Temp temp = JsonbBuilder.create().fromJson(json, Temp.class);
System.out.println(temp.points);
}
public static class Temp {
public String id = null;
public List points = null;
public Temp() {
}
}
}
Since I've already done it: Changing the json format would allow this:
public class Test {
public static void main(String[] args) {
String json = "{\"id\":\"test\",\"points\":[ {\"x\" : 1.0, \"y\" : 2.0 }, {\"x\" : 3.0, \"y\" : 4.0 } ] }";
Temp temp = JsonbBuilder.create().fromJson(json, Temp.class);
System.out.println(temp.points);
}
public static class Temp {
public String id = null;
public List<Point> points = null;
public Temp() { }
}
public static class Point {
public double x;
public double y;
public Point() { }
}
}
I have new requirement, I am creating REST API which has dynamic request (actions) and I want to convert that JSON request to POJO, I know how to convert JSON to POJO where key's are same, but not sure what to do when there are different content on objects.
My Json is as follow.
{
"name":"Workflow",
"actions": [
{
"name": "EDIT_PROPERTY",
"payload": {
"name": "city",
"value": "Pune"
}
},
{
"name":"SEND_EMAIL",
"payload":{
"from":"no-reply#yaho.com",
"to":"alpesh#yahoo.com",
"subject":"Try email",
"body":"content"
}
},
{
"name":"CREATE_TASK",
"payload":{
"user":1,
"type":"call",
"status":"open",
"note":"This is note content"
}
}
]
}
As you can see actions are set of Objects which has name and payload, now payload has different fields, I have predefined names. and each payload under action has predefined keys as you see.
I want to convert this to POJO something like
class Workflow{
String name;
Set<Action> actions;
}
class Action {
String name;
//What to add as payload
}
Thanks
Alpesh
This is what you can do :
JSON to POJO model :
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Event {
public String name;
public List<Action> actions;
#Data
public static class Action {
public String name;
Map<String, Object> payload;
}
}
public class TestJson {
private static String json = "{\n" +
" \"name\":\"Workflow\",\n" +
" \"actions\": [\n" +
" {\n" +
" \"name\": \"EDIT_PROPERTY\",\n" +
" \"payload\": {\n" +
" \"name\": \"city\",\n" +
" \"value\": \"Pune\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"name\":\"SEND_EMAIL\",\n" +
" \"payload\":{\n" +
" \"from\":\"no-reply#yaho.com\",\n" +
" \"to\":\"alpesh#yahoo.com\",\n" +
" \"subject\":\"Try email\",\n" +
" \"body\":\"content\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"name\":\"CREATE_TASK\",\n" +
" \"payload\":{\n" +
" \"user\":1,\n" +
" \"type\":\"call\",\n" +
" \"status\":\"open\",\n" +
" \"note\":\"This is note content\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
#SneakyThrows
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Event event = objectMapper.readValue(json, Event.class);
System.out.println(event);
}
}
When debugging, you'll notice that our objects have been filled accordingly:
Well, generally, I prefer any solution that does not involve Component Annotations. This is my own personal preference because eliminating constructors and method parameters is usually a giant headache. This is based on 1999 - 2006 Java Programming experience. If you have a need to dynamically generate classes and constructors or getters, then you may easily ignore or delete this answer. For me, JSON Parsing is practice right now.
In this posted answer, I have used the older Java JSON Library whose JavaDoc may be viewed here: javax.json.*. Here is my solution. It requires / expects that you write:
Your own toString() methods for your JSON Data Classes
Your own retrieve operations
The following code has output, and that is included at the end of this post. Usually I include a lot of Code Documentation. However, when the code is strictly parsing data, the code itself is usually so legible that more comments would clutter the retrieve and the toString() operations, so I have left them as is.
import java.util.*;
import javax.json.*;
import java.io.*;
public class Messages
{
public abstract static class Action
{
public final String name;
public Action(String name) { this.name=name; }
}
public static class EditProperty extends Action
{
public final String propName, propValue;
public EditProperty(String name, String value)
{ super("EDIT_PROPERTY"); this.propName=name; this.propValue=value; }
public String toString()
{
return
"Action: " + name + '\n' +
"Name: " + propName + '\n' +
"Value: " + propValue + '\n';
}
}
public static class SendEmail extends Action
{
public final String from, to, subject, body;
public SendEmail(String from, String to, String subject, String body)
{ super("SEND_EMAIL"); this.from=from; this.to=to; this.subject=subject; this.body=body; }
public String toString()
{
return
"Action: " + name + '\n' +
"From: " + from + '\n' +
"To: " + to + '\n' +
"Subject: " + subject + '\n' +
"Body: " + body + '\n';
}
}
public static class CreateTask extends Action
{
public final int user;
public final String type, status, note;
public CreateTask(int user, String type, String status, String note)
{ super("CREATE_TASK"); this.user=user; this.type=type; this.status=status; this.note=note; }
public String toString()
{
return
"Action: " + name + '\n' +
"User: " + user + '\n' +
"Type: " + type + '\n' +
"Status: " + status + '\n' +
"Note: " + note + '\n';
}
}
public static void main(String[] argv) throws IOException
{
Vector<Action> actions = new Vector<>();
Reader r = new FileReader("in.json");
JsonArray actionList = Json
.createReader(r)
.readObject()
.getJsonArray("actions");
for (JsonObject actionObj : actionList.getValuesAs(JsonObject.class))
{
JsonObject payload = actionObj.getJsonObject("payload");
Action action = null;
switch (actionObj.getString("name"))
{
case "EDIT_PROPERTY" : action = new EditProperty(
payload.getString("name"),
payload.getString("value")
); break;
case "SEND_EMAIL" : action = new SendEmail(
payload.getString("from"),
payload.getString("to"),
payload.getString("subject"),
payload.getString("body")
); break;
case "CREATE_TASK" : action = new CreateTask(
payload.getInt("user"),
payload.getString("type"),
payload.getString("status"),
payload.getString("note")
); break;
}
actions.add(action);
}
for (Action action : actions) System.out.println(action);
}
}
The class and inner-classes above would produce this output when invoked at the command line:
#cloudshell:~$ java Messages
Action: EDIT_PROPERTY
Name: city
Value: Pune
Action: SEND_EMAIL
From: no-reply#yaho.com
To: alpesh#yahoo.com
Subject: Try email
Body: content
Action: CREATE_TASK
User: 1
Type: call
Status: open
Note: This is note content
The JSON you show is actually a list of one object type;
specifically, the payload is just a Map of String to Object.
Once you parse the JSON,
your code will need to process each "different" payload type
based on the payload type.
Here is some sample code:
public class BlamMessage
{
private String name;
private Map<String, Object> payload;
...
}
public class MessageHolder
{
#JsonProperty("actions")
private List<BlamMessage> messageList;
private String name;
...
}
Hey I have also problem here is my Json
[
{
"aimid": "12345"
},
{
"aimid": "333674"
},
{
"aimid": [
"4568999",
"6789345"
]
}]
and This is my Pojo class:-
#JsonProperty("aimid")
private String aimid;
public String getAimid() {
return aimid;
}
public void setAimid(String aimid) {
this.aimid = aimid;
}
I want to store aimid in pojo . When i am writing like above in my application i am getting error.
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token.
From my understanding i am getting error because of Array element so anyone can suggest me how i can capture both thing if it is coming as String or It is coming as a Array String
The challenge is that in some cases "aimid" is a string value but in another case it is an array.
If you have control over the structure of the JSON then update the structure so that each element of the root array has ONE of the following structures:
String
{
"aimid": "333674"
}
OR array
{
"aimid": [
"4568999",
"6789345"
]
}
If you do not have control of the structure of the data you will need to parse it yourself and process it into your POJO.
Please see these 3 code examples that should illustrate how you can go about this approaches. :
public class MyPojo {
private List<String> aimid;
#JsonProperty("aimid")
public List<String> getAimid() {
return aimid;
}
#JsonProperty("aimid_array")
public void setAimid(final List<String> aimid) {
this.aimid = aimid;
}
#JsonProperty("aimid")
public void setAimid(final String aimid) {
this.aimid = Arrays.asList(aimid);
}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.*;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;
public class UnitTest {
private static final Logger LOGGER = Logger.getLogger(UnitTest.class.getName());
public UnitTest() {
}
#Test
public void testOneAimId() throws IOException {
final String json = "[\n"
+ "{\n"
+ " \"aimid\": \"12345\"\n"
+ "},\n"
+ "{\n"
+ " \"aimid\": \"333674\"\n"
+ "}]";
final List<MyPojo> result = new ObjectMapper().readValue(json, new TypeReference<List<MyPojo>>() {
});
log(Level.SEVERE, LOGGER, "testOneAimId", result);
}
#Test
public void testListAimIds() throws IOException {
final String json = "[\n"
+ "{\n"
+ " \"aimid_array\": [\n" // HERE WE HAVE CHANGED THE JSON PROP NAME
+ " \"4568999\",\n"
+ " \"6789345\"\n"
+ " ]\n"
+ "}]";
final List<MyPojo> result = new ObjectMapper().readValue(json, new TypeReference<List<MyPojo>>() {
});
log(Level.SEVERE, LOGGER, "testListAimIds", result);
}
#Test
public void testMixed() throws IOException {
final String json = "[\n"
+ "{\n"
+ " \"aimid\": \"12345\"\n"
+ "},\n"
+ "{\n"
+ " \"aimid\": \"333674\"\n"
+ "},\n"
+ "{\n"
+ " \"aimid_array\": [\n" // HERE WE HAVE CHANGED THE JSON PROP NAME
+ " \"4568999\",\n"
+ " \"6789345\"\n"
+ " ]\n"
+ "}]";
final List<MyPojo> result = new ObjectMapper().readValue(json, new TypeReference<List<MyPojo>>() {
});
log(Level.SEVERE, LOGGER, "testMixed", result);
}
#Test
public void testMixed2() throws IOException {
final String json = "[\n"
+ "{\n"
+ " \"aimid\": \"12345\"\n"
+ "},\n"
+ "{\n"
+ " \"aimid\": \"333674\"\n"
+ "},\n"
+ "{\n"
+ " \"aimid\": [\n"
+ " \"4568999\",\n"
+ " \"6789345\"\n"
+ " ]\n"
+ "}]";
final JsonNode result = new ObjectMapper().readValue(json, JsonNode.class);
final ArrayList<String> arrayList = new ArrayList<>();
result.forEach((final JsonNode jsonNode) -> {
if (jsonNode.getNodeType() != JsonNodeType.OBJECT)
throw new IllegalArgumentException(jsonNode.toString());
final ObjectNode obj = (ObjectNode) jsonNode;
obj.forEach(o -> {
switch (o.getNodeType()) {
case ARRAY:
final ArrayNode array = (ArrayNode) o;
array.forEach(t -> arrayList.add(t.asText()));
break;
case STRING:
arrayList.add(o.asText());
break;
default:
throw new IllegalArgumentException(o.toString());
}
});
});
final MyPojo myPojo = new MyPojo();
myPojo.setAimid(arrayList);
log(Level.SEVERE, LOGGER, "myPojo", myPojo);
}
private void log(final Level level, final Logger logger, final String title, final Object obj) {
try {
if (title != null)
logger.log(level, title);
final ObjectWriter writer = new ObjectMapper().writerWithDefaultPrettyPrinter();
logger.log(level, obj == null ? "null" : writer.writeValueAsString(obj));
} catch (final JsonProcessingException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
}
First of all the title might not be the best. Feel free to edit.
The problem: Assuming there is this json (quotation is missing, I know):
{
meta: {
code: 200
},
response: {
suggestedFilters: { },
suggestedRadius: 922,
headerLocation: "New York",
headerFullLocation: "New York",
headerLocationGranularity: "city",
totalResults: 246,
groups: [
{
type: "Recommended Places",
name: "recommended",
items: [
{
// item I care
},
{
// item I care
}
]
}
]
}
}
Is it necessary to pass the whole path in the POJO? For example now my class is:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyVenueResponse {
private VenueResponse response;
public VenueResponse getResponse() {
return response;
}
public class VenueResponse{
private List<VenueGroup> groups;
public List<VenueGroup> getGroups() {
return groups;
}
}
public class VenueGroup {
private ArrayList<GroupItems> items;
public ArrayList<GroupItems> getItems() {
return items;
}
}
}
I really do not care about all the medium classes but only about the public ArrayList<GroupItems> getItems(). Is there a way to "shortcut" the process and tell Jackson to skip the "response" and start from the groups object or doesn't know how to map it?
Note that I use databind like:
objectMapper.readValue(body.charStream(), MyVenueResponse.class); // where body is a ResponseBody from OKHttp
You can traverse the input JSON until the certain point using the Jackson Tree API and then convert a sub tree into a Java object. Here is an example:
public class JacksonNestedList {
public final static String JSON = "{\n"
+ " meta: {\n"
+ " code: 200\n"
+ " },\n"
+ " response: {\n"
+ " suggestedFilters: { },\n"
+ " suggestedRadius: 922,\n"
+ " headerLocation: \"New York\",\n"
+ " headerFullLocation: \"New York\",\n"
+ " headerLocationGranularity: \"city\",\n"
+ " totalResults: 246,\n"
+ " groups: [\n"
+ " {\n"
+ " type: \"Recommended Places\",\n"
+ " name: \"recommended\",\n"
+ " items: [\n"
+ " {\n"
+ " key: \"value1\"\n"
+ " },\n"
+ " {\n"
+ " key: \"value2\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
public static class GroupItem {
public String key;
#Override
public String toString() {
return "key:" + key;
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
final JsonParser items = mapper.readTree(JSON)
.path("response")
.path("groups")
.get(0)
.path("items")
.traverse();
System.out.println(mapper.readValue(items, new TypeReference<List<GroupItem>>() {}));
}
}
Output:
[key:value1, key:value2]
I'm having a little trouble working out an appropriate java object structure for the following JSON data:
"pages": {
"181382": {
"pageid": 181382,
"ns": 0,
"title": "Anonymity"
},
"7181837": {
"pageid": 7181837,
"ns": 0,
"title": "Anonymous"
}
}
The identifiers "181382" and "7181837" change depending on the data returned so these cannot be used as a member on an object. I tried to approach it using a Map<String, Object> approach but got a little stuck.
Edit:
This is what I've tried
public class PageData {
int pageid;
String ns;
String title;
public int getPageid() {
return pageid;
}
public String getNs() {
return ns;
}
public String getTitle() {
return title;
}
}
Map<String, PageData> pages = results.getPages().getData();
for (PageData data : pages.values()) {
System.out.println(data.getTitle());
}
Just create some wrapper for your Object. Here is working example:
Wrapper
public class Wrapper {
Map<String, PageData> pages = null;
public Map<String, PageData> getPages() {
return pages;
}
}
Launcher
public class Launcher {
public static void main(String[] args) {
String str = "{\"pages\": {\r\n" +
" \"181382\": {\r\n" +
" \"pageid\": 181382,\r\n" +
" \"ns\": 0,\r\n" +
" \"title\": \"Anonymity\"\r\n" +
" },\r\n" +
" \"7181837\": {\r\n" +
" \"pageid\": 7181837,\r\n" +
" \"ns\": 0,\r\n" +
" \"title\": \"Anonymous\"\r\n" +
" }\r\n" +
" }" +
"}";
Gson gson = new Gson();
Wrapper results = gson.fromJson(str, Wrapper.class);
Map<String, PageData> pages = results.getPages();
for (PageData data : pages.values()) {
System.out.println(data.getTitle());
}
}
}
PageData
public class PageData{/* the same */}
Output:
Anonymity
Anonymous