Using GSON to parse json object vs json array - java

I have a JSON that is either a single object or an array of the same object. Is there a way to parse this data using Gson where it'll distinguish between the single object vs the array?
The only solution I currently have for this is to manually parse the json and surround that with a try catch. First I'll try parsing it as a single object, if it fails, it'll throw an exception and then I'll try to parse it as an array.
I don't want to parse it manually though...that would take me forever.
Here's an idea of what's happening.
public class ObjectA implements Serializable{
public String variable;
public ObjectB[] objectb; //or ObjectB objectb;
public ObjectA (){}
}
Here's the object that can either be an array or a single object.
public class ObjectB implements Serializable{
public String variable1;
public String variable2;
public ObjectB (){}
}
And then when interacting with the json response. I'm doing this.
Gson gson = new Gson();
ObjectA[] objectList = gson.fromJson(response, ObjectA[].class);
When the array of ObjectA's are being serialized, the json contains either an array or single object for ObjectB.
[
{
"variable": "blah blah",
"objectb": {
"variable1": "1",
"variable2": "2"
}
},
{
"variable": "blah blah",
"objectb": {
"variable1": "1",
"variable2": "2"
}
},
{
"variable": "blah blah",
"objectb": [
{
"variable1": "1",
"variable2": "2"
},
{
"variable1": "1",
"variable2": "2"
}
]
}
]

I just changed ObjectB[] to List<ObjectB> into ObjectA declaration.
ArrayList<ObjectA> la = new ArrayList<ObjectA>();
List<ObjectA> list = new Gson().fromJson(json, la.getClass());
for (Object a : list)
{
System.out.println(a);
}
and this is my result:
{variable=blah blah, objectb={variable1=1, variable2=2}}
{variable=blah blah, objectb={variable1=1, variable2=2}}
{variable=blah blah, objectb=[{variable1=1, variable2=2}, {variable1=1, variable2=2}]}
I think that in full generics era, if you do not have particular needs, you can switch from arrays to lists, you have many benefits that Gson also can use to do a flexible parsing.

Try to use com.google.gson.JsonParser.
String jsonString = "object json representation";
JsonParser jsonParser = new JsonParser();
JsonElement jsonElement = jsonParser.parse(jsonString);
if (jsonElement.isJsonArray()) {
// some logic
}
There are different ways to get your object using the JsonElement instance, for example - simply using the com.google.gson.Gson methods :
public <T> T fromJson(com.google.gson.JsonElement json, java.lang.Class<T> classOfT) throws com.google.gson.JsonSyntaxException
public <T> T fromJson(com.google.gson.JsonElement json, java.lang.reflect.Type typeOfT) throws com.google.gson.JsonSyntaxException
So, study the JsonElement, JsonArray, JsonPrimitive, JsonNull and JsonObject classes. Believe, they have an adequate interface to recover your object.

Can you try/catch it by first trying to parse the array, then falling back to parsing the single object class?
You could also do a real simple test and look to the first non whitespace character in the string you are deseralizing, if it is a "{" it is a single object, if it is a "[" it is an array

Related

Deseralize JSON with Jackson into a list of heterogeneous elements

In my JSON I have an element with the following contents:
{
...
"locations": [
[
{
"location_type": "permanent",
"position": "at",
"accuracy": "exact"
},
"and",
{
"location_type": "permanent",
"position": "in",
"accuracy": "exact"
}
],
"or",
{
"location_type": "temporary",
"position": "at",
"accuracy": "exact"
}
],
...
}
As shown, an element of locations can be:
a location
a logical operator
a list of locations (allowing for complex locations)
I'm getting "Cannot deserialize instance of com.example.processor.transformation.json.Location out of START_ARRAY token".
How can I consume this into a data structure using Jackson?
What I tried so far:
Providing a Location(String logicalOperator) constructor helps for a flat list case. (I basically turn the operator into a special value of Location.)
Adding a Location(List<Location> subLocations) or a Location(Location[] subLocations) constructor doesn't help for this case.
Note: I am not in control of the JSON format so I cannot encode it in a more Jackson-friendly way.
You're going to need a custom de-serializer for that. You can't just add a constructor.
Here's a self-contained example with class Foo, that can be either represented by its own property "foo" : "someString" or by some logical operator "and" or "or", etc. as a String literal, intended to represent a Foo instance whose foo property will be the value of that literal.
This may or may not fit your case exactly, but you can adjust.
In other words:
{"foo": "a"} --> new Foo("a")
"or" --> new Foo("or")
Example
// given...
#JsonDeserialize(using=MyDeserializer.class)
class Foo {
String foo;
public void setFoo(String s) {
foo = s;
}
public String getFoo() {
return foo;
}
public Foo(String s) {
setFoo(s);
}
}
// and custom de-serializer...
class MyDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ct)
throws IOException, JsonProcessingException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
// this JSON object has a "foo" property, de-serialize
// injecting its value in Foo's constructor
if (node.has("foo")) {
return new Foo(node.get("foo").asText());
}
// other case, assuming literal (e.g. "and", "or", etc.)
// inject actual node as String value into Foo's constructor
else {
return new Foo(node.asText());
}
}
}
// here's a quick example
String json = "[{\"foo\": \"a\"}, \"or\", {\"foo\": \"b\"}]";
ObjectMapper om = new ObjectMapper();
List<Foo> list = om.readValue(json, new TypeReference<List<Foo>>(){});
list.forEach(f -> System.out.println(f.foo));
Output
a
or
b
Note for clarity
This represents a very simple example.
In your case, you're probably going to want a polymorphic collection of Location POJOs mixed with LogicalOperator POJOs (or something similar), sharing a common marker interface.
You can then decide what object to de-serialize based on whether the JSON node features contents (i.e. a location) or the JSON node is its contents (e.g. the logical operators).

Parse JSON object containing multiple nested objects without making class for each nested object

I'm using GSON in Android to parse a JSON object, part of which contains multiple nested objects holding all of the same fields. For example, the JSON structure looks similar to this:
{
"name": "nestedJSONExample",
"divisions": {
"division1": {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
"division2": {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
...
"division99" {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
}
}
In this example all of the "division##" nested objects contain all of the same fields, is there a way to parse this JSON into a Java class without creating model classes for each "division##" object?
i.e. can I create a Java structure like:
divisions.division##.id
without having to make classes for each individual division?
You seem to have a little confusion: you don't need a mapping class for each division## node since you can reuse one class multiple times regardless the property names. You might need from zero to two custom mapping classes regarding the way you prefer:
0 custom mapping classes if traversing a parsed JSON object on your own;
1 custom mapping class if applying advanced parsing techniques and combining the mapping with type adapters or JSON objects;
2 custom mapping classes for exact mapping.
The examples below are written with Java 8 language features and Java 8 Stream API but can be re-written with Java 6 easily. The JSON constant below is just a String with the following JSON document:
{
"name": "nestedJSONExample",
"divisions": {
"division1": {"id": "id1", "name": "name1", "alsoKnownAs": ["alsoKnownAs1A"]},
"division2": {"id": "id2", "name": "name2", "alsoKnownAs": ["alsoKnownAs2A"]},
"division3": {"id": "id3", "name": "name3", "alsoKnownAs": ["alsoKnownAs3A"]},
"division4": {"id": "id4", "name": "name4", "alsoKnownAs": ["alsoKnownAs4A"]},
"division5": {"id": "id5", "name": "name5", "alsoKnownAs": ["alsoKnownAs5A"]},
"division6": {"id": "id6", "name": "name6", "alsoKnownAs": ["alsoKnownAs6A"]}
}
}
No mappings
JsonElement is a built-in Gson class representing any JSON element. Combining JsonElement class and its child classes elements, Gson can build a JSON tree that reflects a given JSON document structure. So just traversing from the root is enough.
final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, JsonElement.class)
.getAsJsonObject()
.get("divisions") // get the divisions property
.getAsJsonObject()
.entrySet() // and traverse its key/value pairs
.stream()
.map(Entry::getValue) // discarding the keys
.map(JsonElement::getAsJsonObject)
.map(jo -> jo.get("id")) // take the id property from the every `division` object
.map(JsonElement::getAsJsonPrimitive)
.map(JsonPrimitive::getAsString)
.collect(toList());
System.out.println(ids);
Exact mappings
Here you could need just two mapping classes to describe the relations between JSON objects. The divisions node can be just a Map holding arbitrary keys and Division values.
final class OuterWithMap {
//String name;
Map<String, Division> divisions;
}
final class Division {
String id;
//String name;
//List<String> alsoKnownAs;
}
final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, OuterWithMap.class)
.divisions
.values() // use map values only ignoring the keys
.stream()
.map(d -> d.id)
.collect(toList());
System.out.println(ids);
Not exact mappings
This is the most complicated one and shows advanced techniques in parsing JSON with Gson and mapping given JSON documents to mapping classes may not reflect the real structure therefore making transformations on-fly.
final class OuterWithList {
//String name;
#JsonAdapter(NoKeysTypeAdapterFactory.class)
List<Division> divisions;
}
final class NoKeysTypeAdapterFactory
implements TypeAdapterFactory {
// No accessible constructor needed - Gson can instantiate it itself
private NoKeysTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Is it a list?
if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
// Try to determine the list element type
final Type elementType = getElementType(typeToken.getType());
// And create a custom type adapter instance bound to the specific list type
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getNoKeysTypeAdapter(gson, elementType);
return typeAdapter;
}
// Otherwise just tell Gson try to find another appropriate parser
return null;
}
private static Type getElementType(final Type type) {
// Is it a generic type with type parameters?
if ( type instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
// If yes, then just take the first type argument since java.util.List can only one type
return parameterizedType.getActualTypeArguments()[0];
}
// Otherwise java.lang.Object due to either Java generics type erasure or raw types usage
return Object.class;
}
}
final class NoKeysTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final Gson gson;
private final Type elementType;
private NoKeysTypeAdapter(final Gson gson, final Type elementType) {
this.gson = gson;
this.elementType = elementType;
}
static <E> TypeAdapter<List<E>> getNoKeysTypeAdapter(final Gson gson, final Type elementType) {
return new NoKeysTypeAdapter<>(gson, elementType);
}
#Override
public void write(final JsonWriter out, final List<E> value) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
final List<E> list = new ArrayList<>();
// Make sure that the next JSON stream token is `{`
in.beginObject();
// Read until the object ends
while ( in.peek() != END_OBJECT ) {
// Ignore the found JSON object property name
in.nextName();
// And delegate the property value parsing to a downstream parser
final E element = gson.fromJson(in, elementType);
list.add(element);
}
// Make sure that the JSON stream is finished with the `}` token
in.endObject();
return list;
}
}
Using a special querying library
There are some libraries like JsonPath that can make querying JSON documents somewhat easier. JsonPath can work without Gson, however, as far as I understand, it uses another JSON parsing library, and does not parse JSON itself (but I don't know how it actually is). Example of use:
final JsonPath jsonPath = JsonPath.compile("$.divisions.*.id");
final List<String> ids = jsonPath.<JSONArray>read(JSON)
.stream()
.map(o -> (String) o)
.collect(toList());
System.out.println(ids);
All four examples above have the following output:
[id1, id2, id3, id4, id5, id6]
Using GSON your best bet is to write a custom Deserializer (example) or a TypeAdapter (example), this will allow you to do whatever you want with the structure then return a single (top level) object

Deserializing json from retrofit using jackson where same variable name can represent two different objects

The response from retrofit2 may be of the following types.(and we don't know before hand which response will come)
{
"id": "abc",
"place": "LA",
"driverId": "abbabaaan"
}
or
{
"id": "abc",
"place": "LA",
"driverId": {
"name": "xyz",
"id": "jygsdsah",
"car": "merc"
}
}
Is there any way to define a class so that while deserializing jackson will check the type of object "driverId" contains and assigns it to say "driverIdObj" field or "driverIdStr" field in the class.
You could deserialize to a Map. Afterwards, you could inspect the map and decide to which of the 2 types you convert the map. Take a look at this answer: Deserializing JSON based on object type
To convert from Map to Object you can use ObjectMapper::convertValue, e.g
mapper.convertValue(map, Response1.class)
You can check whether the json has values inside it;
String jsonString= "{ ... }";
Object json = new JSONTokener(jsonString).nextValue();
if (json instanceof JSONObject){
//do operations related with object
}
else if (json instanceof JSONArray) {
//do operations based on an array
}
Try this
JSONObject jsonObject = new JSONObject("your Response String");
Object obj = jsonObject.get("driverId"); //handle Exceptions
if (obj instanceof String){
//do String stuff
}
else if (obj instanceof JSONObject) {
//do json object stuff
}
Make some special handling for the driverId field in your response class using the JsonNode class. Something like the following:
public class Response {
private String id, place, driverIdStr;
private DriverIdObj driverIdObj;
// ... Various getters and setters omitted.
public void setDriverId(JsonNode driverId) {
if (driverId.isObject()) {
// Process the complex version of DriverId.
driverIdObj = new DriverIdObj( /* retrieve fields from JsonNode */ );
} else {
// Process the simple version of DriverId
driverIdStr = driverId.asText();
}
}
}
This lets you maintain a normal approach for most of the response, while making it possible to handle the special field with a minimum of pain.

How to use dynamic json value on my POJO class with Gson?

{
"localeCode": "",
"map": {
"DynamicName1": [],
"DynamicName2": [
{
"date": "2016-05-15T00:00:00",
"seqId": 1,
"status": 10
},
{
"date": "2016-05-16T00:00:00",
"seqId": 83,
"status": 10
}
],
"DynamicName3": [],
"DynamicName4": []
},
"respCode": 100,
"respMsg": "success",
"status": 1
}
How to correctly map this kind of json. If you can see that, Dynamic is a dynamic name. So far I have done this :
public class MapModel {
public MapObject map;
public static class MapObject{
public java.util.Map<String, Student> queryStudent;
public static class Student{
public String date;
public String seqId;
public String status;
}
}
}
But when run the app. I'm getting NullPointerException. Can somebody help me?
You're getting the NullPointerException accessing queryStudent of your MapObject inside your MapModel since it's not correctly filled when you're trying to deserialize your Json.
So to solve your problem look at Gson documentation where you can see:
You can serialize the collection with Gson without doing anything
specific: toJson(collection) would write out the desired output.
However, deserialization with fromJson(json, Collection.class) will
not work since Gson has no way of knowing how to map the input to the
types. Gson requires that you provide a genericised version of
collection type in fromJson(). So, you have three options:
Use Gson's parser API (low-level streaming parser or the DOM parser
JsonParser) to parse the array elements and then use Gson.fromJson()
on each of the array elements.This is the preferred approach. Here is
an example that demonstrates how to do this.
Register a type adapter for Collection.class that looks at each of the
array members and maps them to appropriate objects. The disadvantage
of this approach is that it will screw up deserialization of other
collection types in Gson.
Register a type adapter for MyCollectionMemberType and use fromJson()
with Collection.
Since your MapObject containts a java.util.Map but your class itself it's not generic, I think that a good approach for your case is create a Deserializer.
Before this try to clean up your class definition, to provide constructors to make the deserializer easy to build. Your POJO classes could be:
Student class
public class Student{
public String date;
public String seqId;
public String status;
public Student(String date, String seqId, String status){
this.date = date;
this.seqId = seqId;
this.status = status;
}
}
MapObject class
Note: I change you Map definition, since in your Json seems that could be multiple students for each DynamicName (look at DynamicName2 from your question), so I use Map<String,List<Student>> instead of Map<String,Student>:
public class MapObject{
public Map<String,List<Student>> queryStudent;
public MapObject(Map<String,List<Student>> value){
this.queryStudent = value;
}
}
MapModel class
public class MapModel {
public MapObject map;
}
Now create a Deserializer for your MapObject:
public class MapObjectDeserializer implements JsonDeserializer<MapObject> {
public MapObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Map<String,List<Student>> queryStudents = new HashMap<String,List<Student>>();
// for each DynamicElement...
for (Map.Entry<String,JsonElement> entry : json.getAsJsonObject().entrySet()) {
List<Student> students = new ArrayList<Student>();
// each dynamicElement has an Array so convert and add an student
// for each array entry
for(JsonElement elem : entry.getValue().getAsJsonArray()){
students.add(new Gson().fromJson(elem,Student.class));
}
// put the dinamic name and student on the map
queryStudents.put(entry.getKey(),students);
}
// finally create the mapObject
return new MapObject(queryStudents);
}
}
Finally register the Deserializer and parse your Json:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MapObject.class, new MapObjectDeserializer());
Gson gson = builder.create();
MapModel object = gson.fromJson(YourJson,MapModel.class);
DISCLAIMER: For fast prototyping I test this using groovy, I try to keep the Java syntax but I can forget something, anyway I think that this can put you on the right direction.
Hope it helps,

How to get XStream to map back to a typed List?

I'm using XStream to map some JSON back into Java objects. I'll be dealing with a JSON string that might look like this:
{
"widgets":[
{
"widget_name": "Kenny",
"widget_type": "Character"
},
"widget_name": "Apple",
"widget_type": "Fruit"
}
]
}
Update: I believe this JSON could be interpreted as:
This JSON represents an un-named object that contains a widgets object. The widgets object, in turn, contains an array of other un-named objects. Each of these un-named objects contain two properties: widget_name and widget_type.
Each Widget in this widgetlist corresponds to a POJO:
public class Widget {
private String name;
private String type;
// ...etc.
}
I'd like the above JSON string to map back to a List<Widget>, but I can't seem to figure out how to alias the class correctly:
XStream xs = new XStream();
xs.alias("widgetlist", List.class); // Not a List<Widget> !
How can I get the xs mapper to translate widgetlist into a List<Widget>? Thanks in advance.
This piece of code worked for me, however the JSON generated is not similar to the one you have. If you were to use the JsonHierarchicalStreamDriver instead of the JettisonMappedXmlDriver you would get the same JSON as you have in your question. Downside of this is that the JsonHierarchicalStreamDriver cannot read JSON and will give an UnsupportedOperationException if you try.
XStream xstream = new XStream(new JettisonMappedXmlDriver());
xstream.setMode(XStream.NO_REFERENCES);
xstream.alias("widgetlist", List.class);
xstream.alias("widget", Widget.class);
List<Widget> widgetlist = new ArrayList<Widget>();
Widget w1 = new Widget("Kenny", "Character");
Widget w2 = new Widget("Apple", "Fruit");
widgetlist.add(w1);
widgetlist.add(w2);
String serialized = xstream.toXML(widgetlist);
System.out.println(serialized);
List<Widget> unserialized = (List<Widget>)xstream.fromXML(serialized);
System.out.println("Size: "+unserialized.size());
The generated XML by JettionMappedXmlDriver
{
"widgetlist": {
"widget": [
{
"name": "Kenny",
"type": "Character"
},
{
"name": "Apple",
"type": "Fruit"
}
]
}
}
Interpretation
The JSON is the DROP_ROOT_MODE version of the below JSON(un-named object, if you like).
You can therefore look at it as an un-named object that contains a list of Widget. The Widget object still have the same structure as described in the question.
public class WidgetList {
List<Widget> widgets;
public WidgetList() {
this.widgets = new ArrayList<>();
}
}
Xstream settings
xstream.setMode(XStream.NO_REFERENCES);
xstream.alias("widgets", List.class);
xstream.addImplicitCollection(WidgetList.class, "widgets");
xstream.alias("widgets", Widget.class);
xstream.alias("widgetsL", WidgetList.class);
JSON Output
{
"widgetsL": {
"widgets": [
{
"name": "Kenny",
"type": "Character"
},
{
"name": "Apple",
"type": "Fruit"
}
]
}
}

Categories