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.
Related
I'm using Jackson and MongoDB in Java. I want to serialize part of an object as "normal JSON" with Jackson, while a specific property should be serialized as "extended JSON". The reason being to keep the data types of that particular part of the structure intact (i.e. not lose info on e.g. Double vs Int64 vs Int32).
For example, if I have a class:
public class ExampleClassToBeSerialized {
#JsonProperty("itemList")
public List<MyObjectIntendedAsExtendedJson> itemList;
#JsonProperty("something")
public boolean something;
#JsonProperty("somethingElse")
public long somethingElse;
}
public class MyObjectIntendedAsExtendedJson {
public long thisShouldBeExtendedJson;
}
Here I'd like the outer object with something and somethingElse to be plain JSON, while the serialization of itemList should be done as extended JSON, again, to keep datatypes since it might again be used in MongoDB. I'd expect a result similar to this:
{
"itemList": [{
"thisShouldBeExtendedJson": { "$numberLong": "66666666666" }
},{
"thisShouldBeExtendedJson": { "$numberLong": "77777777777" }
}],
"something": false,
"somethingElse": 123123123123
}
I'm unsure how to achieve this particular result. I've managed to get e.g. the itemList as strings which contain extended JSON by using a StdSerializer<MyObjectIntendedAsExtendedJson> and creating e.g. a Document d = new org.bson.Document() and calling d.toJson(JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED).build()). As mentioned, this ends up being an extended JSON object inside a string. I'd like it to be part of the overall string representing the total JSON. In my example only the list would be used for MongoDB interaction, and therefor only that part needs to be extended JSON.
Is there any way to achieve my particular case of part plain JSON, part extended JSON?
This is what my class looks like -
public class A {
private Map<String, Object> objects = null;
....
}
My json would be like -
{
"f1" : {
"name" : "some name",
"val" : 3
},
"f2" : {
"arg": {
some field/value pairs
}
}
}
What I want is to specify in the JSON itself the type to which it can be deserialized to. So the value for f1 would be converted to an object of class B and f2 would get converted to object of C.
My code will look like this -
Object o = objects.get("f1");
if (o instanceof B) {
...
} else if (o instanceof C) {
...
}
Is there a way to do this? I want the json to control the deserialization.
Yes, Jackson can use a type identifier if JSON document has it. This is usually done by using annotation #JsonTypeInfo.
There are multiple ways to add/use type identifier, both regarding how it is included in JSON document, and as to what kind of id is being used (type name or Java class name?).
The easiest way to see how things match is to actually start with a POJO, add #JsonTypeInfo annotation, and serialize it to see kind of JSON produced. And once you understood how inclusion works you can modify, if necessary, structure of JSON and/or Java class definition.
Given the following JSON object
{
"id": 5,
"data: { ... }
}
Is it possible to map this to the following POJO?
class MyEntity {
int id;
Map<String, Object> data;
}
Because I would like to leave the data object open ended. Is this even possible or what is a better approach to go about this? I am doing this on Android.
I don't have any idea about Android application but you can achieve it using Gson library easily.
The JSON that is used in your post is not valid. It might be a typo. Please validate it here on JSONLint - The JSON Validator
Simply use Gson#fromJson(String, Class) method to convert a JSON string into the object of passed class type.
Remember the name of instance member must be exactly same (case-sensitive) as defined in JSON string as well. Read more about JSON Field Naming
Use GsonBuilder#setPrettyPrinting() that configures Gson to output Json that fits in a page for pretty printing.
Sample code:
String json = "{\"id\": 5,\"data\": {}}";
MyEntity myEntity = new Gson().fromJson(json, MyEntity.class);
String prettyJsonString = new GsonBuilder().setPrettyPrinting().create().toJson(myEntity);
System.out.println(prettyJsonString);
output:
{
"id": 5,
"data": {}
}
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));
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.