GSON How to deserialize to a class with this array json string - java

I am struggling to find a way to serialize / deserialize this JSON output to a Java class? Can anyone provide code sample?
[
{
"topic": "this is my topic"
},
[
{
"name": "John"
},
{
"age": 100
}
]
]
My current attempt uses this Javabean:
public class Test {
private String topic;
private List<Person> listOfPersons;
}
Which I try to deserialize data into using Gson:
gson.fromJson(this.json, Test[].class);
But the deserialization fails, because Gson is looking for a list of persons in the JSON, but it doesn't exist.

It doesn't seem like having an object next to an array, inside an array, is sensical. It would make sense to put things this way:
{
"topic": "this is my topic",
"listOfPersons" : [
{
"name": "John",
"age": 100
},
{
... another person
}
]
}
Then you could just have a Person class:
public class Person {
private String name;
private int age;
}
...and you could deserialize with the code you already have.

The problem here is that your JSON data is syntactically correct, but semantically ambiguous. And by that I mean, it appears to represent a polymorphic array, where each item in the array is of a different type.
In addition, the portion representing a 'person' seems to have been de-normalized horribly, where each attribute of a person is represented as a separate object in an array. Quite weird indeed. Unfortunately its really impossible to tell what types are represented just by looking at the data alone, and there are no hints provided to allow Gson to deserialize the data for you. The best you can do in this case is manually parse the information.
Test test = new Test();
JsonArray rootArray = new JsonParser().parse(jsonString);
test.setTopic(rootArray.get(0).get("topic");
Person person = new Person();
JsonArray personArray = rootArray.get(1);
person.setName(personArray.get(0).get("name"));
person.setAge(personArray.get(1).get("age"));
test.setListOfPersons(Arrays.asList(person));

Related

How to structure class for deserializing this nested array?

I'm trying to deserialize a nested array from a JSON response. It's the first time I've ever gotten an array of arrays and I'm not quite sure how to structure my class to handle it.
{
"prices": [
[
1641670404234,
0.01582586939240936
],
[
1641674037525,
0.015999047707867396
],
[
1641677655158,
0.016072905257982606
]
...
],
}
If the brackets were { instead of [
{
"prices": {
{
1641670404234,
0.01582586939240936
},
{
1641674037525,
0.015999047707867396
},
{
1641677655158,
0.016072905257982606
}
}
...
}
I could use
#SerializedName("prices")
private List<Price> prices;
public class Price {
private long date;
private BigDecimal price;
}
However since it is [ instead, I am quite unsure how to structure it.
I've tried adding another List wrapper to it but that throws an error
#SerializedName("prices")
private List<List<Price>> prices;
IllegalStateException: Expected BEGIN_OBJECT but was NUMBER at line 1 column 26 path $.prices[0][0]
I've also tried wrapping it with a JSONArray
#SerializedName("prices")
private List<JSONArray<Price>> prices;
but that's not quite right
I've tried searching other SO answers but I could not find any examples where it's two consecutive [ [ brackets.
They are all { [ or [ {.
What's the correct way to do it?
The proper way to solve this is to write a custom TypeAdapter for your Price class. This has the advantage that you can keep your model classes as is (with a List<Price> prices field), and have them represent more closely the actual data. If instead you parsed the JSON data as List<List<BigDecimal>> or similar, then you would have to manually validate that the JSON data is wellformed and have to convert the List<BigDecimal> to a Price object yourself.
Here is how a TypeAdapter implementation for your Price class could look like:
class PriceTypeAdapter extends TypeAdapter<Price> {
#Override
public void write(JsonWriter out, Price value) throws IOException {
out.beginArray();
out.value(value.date);
out.value(value.price);
out.endArray();
}
#Override
public Price read(JsonReader in) throws IOException {
in.beginArray();
Price priceObj = new Price();
priceObj.date = in.nextLong();
// nextString() automatically converts JSON numbers to String, if necessary
// This is similar to how Gson's default adapter for BigDecimal works
priceObj.price = new BigDecimal(in.nextString());
in.endArray();
return priceObj;
}
}
Note: Alternatively to reading the BigDecimal manually as shown here, you could create this type adapter inside a TypeAdapterFactory and get the default Gson adapter for BigDecimal. This allows reusing Gson's built-in adapters inside your own type adapter, but here for BigDecimal that overhead is probably not worth it.
You can then either register your adapter on a GsonBuilder instance or you can place an #JsonAdapter annotation on your Price class, which references the adapter. In case you use the GsonBuilder approach, you might want to create a null-safe variant of your adapter by calling nullSafe() on it (or you implement null handling in the adapter manually).
Assuming this is the correct JSON:
{
"prices": [
[
1641670404234,
0.01582586939240936
],
[
1641674037525,
0.015999047707867396
],
[
1641677655158,
0.016072905257982606
]
]
}
Then you can use this model to deserialize data to:
JAVA:
public class PricesModel {
public ArrayList<ArrayList<Double>> prices;
}
KOTLIN:
data class PricesModel (
#SerializedName("prices" ) var prices : ArrayList<ArrayList<Double>> = arrayListOf()
)
Handy JSON converters to Java and Kotlin.

Changing JSON array to to a pojo object array

I am getting API response on the following format. I am working on Android project
{
Course: [
{
"Year": 2017,
"CourseTitle": "..",
"Quarter": "autumn",
},
{
"Year": 2017,
"CourseTitle": "..",
"Quarter": "autumn",
}
],
Instructor: {
"name": ,
"address":
}
}
My goal is to get Course array of courses, and assign it to the POJO (plain old java object) that I created using online tool. I am also using the GSON library. I did find different implementation online, and I finally used this, and I was able to filter the nested object "Courses", which holds an array object of courses.
JsonParser parser = new JsonParser();
JsonObject element = (JsonObject)parser.parse(response);
JsonElement responseWrapper = element.getAsJsonArray("Courses");
When I look responseWrapper using debugger tool, it holds a JSON array of course (objects). I wanted to assign these data to the "Course" POJO class I created
public class Course implements Parcelable {
private String href;
private int year;
private String courseTitle;
private String quarter;
private String courseTitleLong;
private String curriculumAbbreviation;
private String courseNumber;
}
I added the following line.
Course[] courseList = gson.fromJson(responseWrapper,Course[].class);
CourseList is an array of Course object, but when I look what I got using debugger tool all the field variables are "null". How could I solve this problem? Is there a better way I should have approached? The whole purpose is getting array of course objects so I could manipulate it for display.
Try to rename your fields to exactly match the json response, even case-wise.

Parsing Dynamically wrapped JSON array

I have an Abstract class with many concrete implementations:
public abstract Ticket {
private Long id;
private Currency fine;
...
}
public class SpeedingTicket extends Ticket {
public Currency getFine(){
// Expensive!
...
}
}
public class ParkingTicket extends Ticket {
public Currency getFine(){
// Eh, not so bad
...
}
}
When the concrete classes are serialized into JSON, it is wrapped with the classes simple name (speedingTickets or parkingTickets):
"_embedded": {
"speedingTickets" :
[{
"id":1,
"fine": "$190",
...,
},
{
"id":2,
"fine": "$100",
...,
}]
}
or
"_embedded": {
"parkingTickets" :[{
"id":100,
"fine": "$15",
...,
}]
}
Since I do not know, at runtime, which Ticket implementation I am receiving back, how can I parse the JSON out using the JSON Response API given the array is wrapped with the concrete implementations simple name?
I have a hack where I take the String value of the JSON and do String operations (substring, indexOf, etc) on it to return only what's in between the braces ("[...]"). I know there's a better way to do this...
After some research, I think I'll try the following tomorrow to see if it works:
JsonNode rootNode = mapper.readTree(jsonResponse);
String classImpl = Iterables.get(rootNode.get("_embedded").fields(), 0).textValue()
I can then say List<Ticket> tickets = response.readAsList(jsonResponse, "_embedded",classImpl) which should allow me to parse the JSON into a List
If you're using Jackson (as your tag suggests), you want to use Polymorphic Deserialization - which is exactly the problem of knowing how to deserialize to the correct subtype.
For example:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
class { }
What this essentially does is include the class name in your JSON, so the deserializer has enough information to properly choose the subclass to instantiate. Something like this:
"_embedded": {
"parkingTickets" :[{
"_type": "ParkingTicket.class",
"id":100,
"fine": "$15",
...,
}]
}
You can just check the type by checking the variable the response contains.
JSONObject jsonObj = new JSONObject(response);
if(jsonObj.has("speedingTickets")){
// parse speedingTickets
}else if(jsonObj.has("parkingTickets")){
// parse parkingtickets
}
A JSON object is an unordered set of key/value pairs. A JSON array is an ordered collection of values. The values themselves could be objects or arrays.
In java it is easy to parse json with org.json library https://github.com/stleary/JSON-java
Short example how to parse json array:
String str = "{ \"number\": [3, 4, 5, 6] }";
JSONObject obj = new JSONObject(str);
JSONArray arr = obj.getJSONArray("number");
for (int i = 0; i < arr.length(); i++)
System.out.println(arr.getInt(i));

desrialize JSON with child array using Jackson annotations?

I'm trying to parse some JSON containing a nested array. I'd like the array to map to a list of child objects within the parent I'm mapping. Here is the (slightly abbreviated) JSON and Java classes
JSON:
{
"id": "12121212121",
"title": "Test Object",
"media$content": [
{
"plfile$audioChannels": 1,
"plfile$audioSampleRate": 18000,
},
{
"plfile$audioChannels": 2,
"plfile$audioSampleRate": 48000,
},
{
"plfile$audioChannels": 2,
"plfile$audioSampleRate": 48000,
}
]
}
Java classes
class MediaObject {
#JsonProperty("id")
private String id;
#JsonProperty("title")
private String title;
#JsonProperty("media$Content")
private List<MediaContent> mediaContent;
... getters/setters ...
}
class MediaContent {
#JsonProperty("plfile$audioChannels")
private int audioChannels;
#JsonProperty("plfile$audioSampleRate")
private int audioSampleRate;
... getters/setters ...
}
I'd like to be able to deserialize using annotations along with the standard mapper code, i.e.
mapper.readValue(jsonString, MediaObject.class)
Everything works fine with the "id" and "title" fields, but my list of MediaContent objects always comes up null. This seems like something Jackson should be able to handle without much trouble, can anyone see what I'm doing wrong here?
The name of the json field is wrong - the attribute is not media$Content, rather media$[c]ontent. Otherwise I do not see why it will not work.

Configure XStream to dynamically map to different objects

I am hitting a RESTful 3rd party API that always sends JSON in the following format:
{
"response": {
...
}
}
Where ... is the response object that needs to be mapped back to a Java POJO. For instance, sometimes the JSON will contain data that should be mapped back to a Fruit POJO:
{
"response": {
"type": "orange",
"shape": "round"
}
}
...and sometimes the JSON will contain data that should be mapped back to an Employee POJO:
{
"response": {
"name": "John Smith",
"employee_ID": "12345",
"isSupervisor": "true",
"jobTitle": "Chief Burninator"
}
}
So depending on the RESTful API call, we need these two JSON results mapped back to one of the two:
public class Fruit {
private String type;
private String shape;
// Getters & setters for all properties
}
public class Employee {
private String name;
private Integer employeeId;
private Boolean isSupervisor;
private String jobTitle;
// Getters & setters for all properties
}
Unfortunately, I cannot change the fact that this 3rd party REST service always sends back a { "response": { ... } } JSON result. But I still need a way to configure a mapper to dynamically map such a response back to either a Fruit or an Employee.
First, I tried Jackson with limited success, but it wasn't as configurable as I wanted it to be. So now I am trying to use XStream with its JettisonMappedXmlDriver for mapping JSON back to POJOs. Here's the prototype code I have:
public static void main(String[] args) {
XStream xs = new XStream(new JettisonMappedXmlDriver());
xs.alias("response", Fruit.class);
xs.alias("response", Employee.class);
// When XStream sees "employee_ID" in the JSON, replace it with
// "employeeID" to match the field on the POJO.
xs.aliasField("employeeID", Employee.class, "employee_ID");
// Hits 3rd party RESTful API and returns the "*fruit version*" of the JSON.
String json = externalService.getFruit();
Fruit fruit = (Fruit)xs.fromXML(json);
}
Unfortunately when I run this I get an exception, because I have xs.alias("response", ...) mapping response to 2 different Java objects:
Caused by: com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException: No such field me.myorg.myapp.domain.Employee.type
---- Debugging information ----
field : type
class : me.myorg.myapp.domain.Employee
required-type : me.myorg.myapp.domain.Employee
converter-type : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path : /response/type
line number : -1
version : null
-------------------------------
So I ask: what can I do to circumvent the fact that the API will always send back the same "wrapper" response JSON object? The only thing I can think of is first doing a String-replace like so:
String json = externalService.getFruit();
json = json.replaceAll("response", "fruit");
...
But this seems like an ugly hack. Does XStream (or another mapping framework) provide anything that would help me out in this particular case? Thansk in advance.
There are two ways with Jackson:
test manually that the wanted keys are there (JsonNode has the necessary methods);
use JSON Schema; there is one API in Java: json-schema-validator (yes, that is mine), which uses Jackson.
Write a schema matching your first object type:
{
"type": "object",
"properties": {
"type": {
"type": "string",
"required": true
},
"shape": {
"type": "string",
"required": true
}
},
"additionalProperties": false
}
Load this as a schema, validate your input against it: if it validates, you know you need to deserialize against your fruit class. Otherwise, make the schema for the second item type, validate against it as a security measure, and deserialize using the other class.
There are code examples for the API, too (version 1.4.x)
If you do know the actual type, it should be relatively straight-forward with Jackson.
You need to use a generic wrapper type like:
public class Wrapper<T> {
public T response;
}
and then the only trick is to construct type object to let Jackson know what T there is.
If it is statically available, you just do:
Wrapper<Fruit> wrapped = mapper.readValue(input, new TypeReference<Wrapper<Fruit>>() { });
Fruit fruit = wrapped.response;
but if it is more dynamically generated, something like:
Class<?> rawType = ... ; // determined using whatever logic is needed
JavaType actualType = mapper.getTypeFactory().constructGenericType(Wrapper.class, rawType);
Wrapper<?> wrapper = mapper.readValue(input, actualType);
Object value = wrapper.response;
but either way it "should just work". Note that in latter case you may be able to use base types ("? extends MyBaseType"), but in general dynamic type can't be specified.

Categories

Resources