Deseralize JSON with Jackson into a list of heterogeneous elements - java

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).

Related

Jackson deserialization of union object with sibling type

I'm trying to combine a few features of Jackson such that I can deserialize a {type,value} pair in json into a Union type in java representing all the information but can't work out how to do it. Help would be greatly appreciated.
Here is what I'm working with:
The java Union type enforces that only a single value can be set at any one time, think of this like an enum but with dynamic data.
The value of the union can take any number of types, some scalar and some object or collection.
Multiple options for the union can share the same type, i.e. noyes and offon are both boolean types in the example below.
I'm not in control of the json structure in any way (it's the data passed to/from an external API) so can't change it at all.
The java classes are generated code from Thrift idl so I can't add annotations to them or adjust their structure drastically. I am in control of the idl though, but would like to keep it fairly clean and free of leaky patterns like the {type, value} that is needed for json but not strongly typed languages.
The type names used in the json (and some of the field names) conflict with java (and other language) keywords which is why each value is suffixed: noyes -> noYesValue, float -> floatValue
A concrete example
I have a json document that looks like this:
{
"obj": [
{
"type": "noyes",
"value": true,
"id": 1
}, {
"type": "offon",
"value": false,
"id": 2
}, {
"type": "text",
"value": "hello",
"id": 3
}, {
"type": "float",
"value": 1.2,
"id": 4
}, {
"type": "times",
"value": [{"s": "12:22", "e": "16:00"}]
"id": 5
}
]
}
And java classes that look like this:
class Response {
List<Item> obj;
}
class Item {
int id;
Value value;
}
class Value {
enum Fields { NO_YES_VALUE, OFF_ON_VALUE, TEXT_VALUE, FLOAT_VALUE, TIMES_VALUE }
static Item noYesValue(boolean noYes) {...}
static Item offOnValue(boolean offOn) {...}
static Item textValue(String text) {...}
static Item floatValue(float value) {...}
static Item timesValue(List<TimesValue> times) {...}
Fields setField;
Object fieldValue;
Value() {}
Value(Fields field, Object value) {setFieldValue(field, value);}
Fields getSetField() { return setField; }
Object getFieldValue() { return fieldValue; }
void setFieldValue(Fields field, Object value) {
// checkType(field, value);
this.setField = field;
this.fieldValue = value;
}
// these do have checks for the set field, types, null, etc
boolean getNoYesValue() { return (Boolean) fieldValue; }
void setNoYesValue(boolean v) { setField = NO_YES_VALUE; fieldValue = v; }
boolean getOffOnValue() { return (Boolean) fieldValue; }
void setOffOnValue(boolean v) { setField = OFF_ON_VALUE; fieldValue = v; }
// ...
}
class TimesValue {
String startTime;
String endTime;
}
With the java classes the following are equivalent:
Value.noYesValue(false);
new Value().setNoYesValue(false);
new Value().setFieldValue(NO_YES_VALUE, false);
new Value(NO_YES_VALUE, false);
Similarly the following are equivalent:
value.getNoYesValue();
(Boolean) value.getFieldValue();
Partially working code (does what I want but only with simple types)
After some more digging and trial and error I've managed to get farther than I have before.
First things first, I flatten out the Value into the Item
interface ItemMixin {
#JsonUnwrapped Value getValue();
}
This hoists all my Value properties so they become part of the Item class, the equivalent of {"value": {"type": "offon", "value": false}} becoming {"type": "offon", "value": false}
Next I need to deal with that Fields enum, to do this I wrote a custom Deserializer which looked like this and register it with a module
new StdDeserializer<Value.Fields>(Value.Fields.class) {
#Override
public Value.Fields deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String type = p.getText();
switch (type) {
case "noyes": return Value.Fields.NO_YES_VALUE;
case "offon": return Value.Fields.OFF_ON_VALUE;
case "float": return Value.Fields.FLOAT_VALUE;
case "text": return Value.Fields.TEXT_VALUE;
case "times": return Value.Fields.TIMES_VALUE;
// ...
}
ctxt.handleWeirdStringValue(Value.Fields.class, type, "Unsupported Value type");
return null;
}
}
Finally I tell the Value type to use the constructor to create the object
static abstract class ValueMixin {
#JsonCreator
ValueMixin(#JsonProperty("type") Value.Fields type, #JsonProperty("value") Object value) {}
}
This gives me 90% of what I need, however the issue I now have is that this only works for primitive types (boolean, String, etc), my times field just gets deserialized as a String causing exceptions in my code.
I've tried using #JsonTypeInfo and #JsonSubTypes on the value parameter, creating a property for that field and putting the annotations there but can't get the creator to correctly resolve the type needed.

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.

Using GSON to parse json object vs json array

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

Custom Jackson serializer for a generic tree

Say I have a parametrized tree implemented in Java as follows:
public class Tree<E> {
private static class Node {
E element;
List<Node> children.
}
Node root;
//... You get the idea.
}
The idea here is that the implementation above is only concerned with the topology of the tree, but does not know anything about the elements that will be stored in the tree by an instantiation.
Now, say I want my tree elements to be geographies. The reason they are organized in trees is because continents contain countries, countries contain states or a provinces, and so on. For simplicity, a geography has a name and a type:
public class GeoElement { String name; String type; }
So that, finally, the geo hierarchy looks like so:
public class Geography extends Tree<GeoElement> {}
Now to Jackson serialization. Assuming the Jackson serializer can see the fields, the direct serialization of this implementation will look like this:
{
"root": {
"element": {
"name":"Latin America",
"type":"Continent"
}
"children": [
{
"element": {
"name":"Brazil",
"type":"Country"
},
"children": [
// ... A list of states in Brazil
]
},
{
"element": {
"name":"Argentina",
"type":"Country"
},
"children": [
// ... A list of states in Argentina
]
}
]
}
This JSON rendering is no good because it contains the unnecessary artifacts from the Tree and Node classes, i.e. "root" and "element". What I need instead is this:
{
"name":"Latin America",
"type":"Continent"
"children": [
{
"name":"Brazil",
"type":"Country"
"children": [
// ... A list of states in Brazil
]
},
{
"name":"Argentina",
"type":"Country"
"children": [
// ... A list of states in Argentina
]
}
]
}
Any help is most appreciated. -Igor.
What you need is #JsonUnwrapped.
Annotation used to indicate that a property should be serialized "unwrapped"; that is, if it would be serialized as JSON Object, its properties are instead included as properties of its containing Object
Add this annotation to the root field of Tree & element field of Node classes as follows:
public class Tree<E> {
private static class Node {
#JsonUnwrapped
E element;
List<Node> children.
}
#JsonUnwrapped
Node root;
//... You get the idea.
}
And it will give you your desired output:
{
"name": "Latin America",
"type": "Continent",
"children": [{
"name": "Brazil",
"type": "Country",
"children": []
}, {
"name": "Argentina",
"type": "Country",
"children": []
}]
}
Perhaps use #JsonValue like so:
public class Tree<E> {
#JsonValue
Node root;
}
if all you need is to just "unwrap" your tree?
Your best bet will be to build and register a custom serializer for your objects.
Define your serializer:
public class NodeSerializer extends StdSerializer<Node> {
Then on your Node class:
#JsonSerialize(using = NodeSerializer.class)
public class Node {
}
And inside of the NodeSerializer
#Override
public void serialize(
Node node, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("name", node.element.name);
jgen.writeStringField("type", node.element.type);
//Process children
serializeFields(node, jgen, provider);
jgen.writeEndObject();
}
This general framework will let you control how the elements get serialized. You may need to #JsonIgnore the element object inside of the Node as well since your custom serializer is taking care of pushing that info into the resulting JSON. There is a lot online about custom serializers and overriding default JSON export.
You can get rid of root in a similar way with a serializer for the Tree implementation.
If you don't want to register the serializer on the class you can also do it on a one at a time basis using the ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Node.class, new NodeSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(tree);
The annotation approach will apply globally. This approach allows some control of how/where your custom serializer is used.
For removing the element type, one possibility would be to change your structure so that the name and the type will be directly included in each node:
public class TreeGeo {
private static class Node {
String name;
String type;
List<Node> children.
}
Node root;
}
For removing the root type, I don't know. I suppose that you could extract a sub-object from the jsonObject but I don't know much about Jackson. However, you could give it a better name like world or manipulate the resulting string to remove it manually with some string manipulations.

Categories