I have a situation where I need to parse an array of JSON objects that are not identical.
So for example:
[
{ "type": "type1", ..... type1 contents .... },
{ "type": "type2", ..... type2 contents .... },
....
{ "type": "type1", ..... type1 contents .... }
]
The number of types is limited and the contents of each type are well can be defined but it is not possible to define a single type of object that will hold the contents.
Is there a way to parse them with Jackson?
P.S. I am trying to avoid writing a custom parser if I can help it.
I would use
com.fasterxml.jackson.databind.JsonNode.
JsonNode parsed = objectMapper
.readValue("[{\"name\": \"a\"},{\"type\":\"b\"}]", JsonNode.class);
This class has tons of utility methods to work with.
Or specific for arrays you can use:
com.fasterxml.jackson.databind.node.ArrayNode
ArrayNode value = objectMapper
.readValue("[{\"name\": \"a\"},{\"type\":\"b\"}]", ArrayNode.class);
EDIT
Sorry, I have misread your question, you can use #JsonTypeInfo for polymorphic serialization/deserialization:
public static void main(String args[]) throws JsonProcessingException {
//language=JSON
String data = "[{\"type\":\"type1\", \"type1Specific\":\"this is type1\"},{\"type\":\"type2\", \"type2Specific\":\"this is type2\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<BaseType> parsed = objectMapper.readValue(data, new TypeReference<List<BaseType>>() {});
System.out.println(parsed);
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Type1.class, name = "type1"),
#JsonSubTypes.Type(value = Type2.class, name = "type2")
})
static public abstract class BaseType {
public String type;
}
static public class Type1 extends BaseType {
public String type1Specific;
#Override
public String toString() {
return "Type1{" +
"type1Specific='" + type1Specific + '\'' +
'}';
}
}
static public class Type2 extends BaseType {
public String type2Specific;
#Override
public String toString() {
return "Type2{" +
"type2Specific='" + type2Specific + '\'' +
'}';
}
}
Here are the docs:
https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
Hope this helps.
And the result would be:
[Type1{type1Specific='this is type1'}, Type2{type2Specific='this is type2'}]
We can use List.class to map this JSON Array with different types of objects that hold the content. It will return the List of LinkedHashMaps. Different content will be mapped to LinkedHashMap.
#Test
public void testLoadCustom() {
String json = "[{\"a\":\"A\" } , {\"b\":\"B\" , \"c\":\"C\" } , {\"d\":\"D\" } ]";
try {
List custom = objectMapper.readValue(json, List.class);
System.out.println(custom);
} catch (Exception ex) {
ex.getStackTrace();
}
}
// Output [{a=A}, {b=B, c=C}, {d=D}]
Related
I have to be able, on the backend side, to receive/send a JSON structure, like that one below.
{
"firstObject":{
"type":"string",
"value":"productValue"
},
"secondObject":{
"type":"string",
"value":"statusValue"
},
"thirdObject":{
"type":"map",
"value":{
"customerName":{
"type":"string",
"value":"customerValue"
}
}
},
"fourthObject":{
"type":"map",
"value":{
"firstObject":{
"type":"map",
"value":{
"anotherObj1":{
"type":"string",
"value":"TEST"
},
"anotherObj2":{
"type":"date",
"value":"01/12/2018"
},
"anotherObj3":{
"type":"date",
"value":"31/01/2018"
}
}
}
}
}
}
The problem that makes this a little bit tricky it's the fact that, for each object, I have to know what kind of type is. There could be 4 types:
int
string
boolean
map
If the value for an object is map (selected by a customer), for example, on the frontend side will appear another key/value structure, so what I'm gonna receive on the backend side is a dynamic structure. I will need to have a validation for this structure, to check if it complies to what I`m expected to receive.
I would appreciate an opinion if I should use just a Java class, to make my objects that I need, or to use beside that, Jackson for JSON validation and mapping all that objects into a JSON.
If I'll use Jackson, I`ll have to make a custom serializer and deserializer.
From Jackson library you can use JsonTypeInfo and JsonSubTypes annotations. They are handle polymorphic type handling:
#JsonTypeInfo is used to indicate details of what type information is included in serialization
#JsonSubTypes is used to indicate sub-types of annotated type
#JsonTypeName is used to define logical type name to use for annotated class
Your example fits for this solution except root object which looks more like simple POJO class. In your case we should craete type structure which help work with these 3 types: string, date, map:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = StringValue.class, name = "string"),
#JsonSubTypes.Type(value = DateValue.class, name = "date"),
#JsonSubTypes.Type(value = MapValue.class, name = "map")
})
abstract class HasValue<T> {
protected T value;
public HasValue() {
this(null);
}
public HasValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
#Override
public String toString() {
return getClass().getSimpleName() + "{" +
"value=" + value +
"}";
}
}
class StringValue extends HasValue<String> {
public StringValue() {
this(null);
}
public StringValue(String value) {
super(value);
}
}
class DateValue extends HasValue<String> {
public DateValue(String value) {
super(value);
}
public DateValue() {
this(null);
}
}
class MapValue extends HasValue<Map<String, HasValue>> {
public MapValue(Map<String, HasValue> value) {
super(value);
}
public MapValue() {
this(new LinkedHashMap<>());
}
public void put(String key, HasValue hasValue) {
this.value.put(key, hasValue);
}
}
Now, we need to introduce POJO for root value. It could look like below, but you can add getters/setters if you want. For this example below will code be enough:
class Root {
public HasValue firstObject;
public HasValue secondObject;
public HasValue thirdObject;
public HasValue fourthObject;
#Override
public String toString() {
return "Root{" +
"firstObject=" + firstObject +
", secondObject=" + secondObject +
", thirdObject=" + thirdObject +
", fourthObject=" + fourthObject +
'}';
}
}
Now, we can finally try to serialise and deserialise these objects:
MapValue customerName = new MapValue();
customerName.put("customerName", new StringValue("customerValue"));
MapValue innerMap = new MapValue();
innerMap.put("anotherObj1", new StringValue("TEST"));
innerMap.put("anotherObj2", new DateValue("01/12/2018"));
innerMap.put("anotherObj3", new DateValue("31/01/2018"));
MapValue fourthObject = new MapValue();
fourthObject.put("firstObject", innerMap);
Root root = new Root();
root.firstObject = new StringValue("productValue");
root.secondObject = new StringValue("statusValue");
root.thirdObject = customerName;
root.fourthObject = fourthObject;
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
System.out.println(json);
System.out.println(mapper.readValue(json, Root.class));
Aboce code prints JSON:
{
"firstObject" : {
"type" : "string",
"value" : "productValue"
},
"secondObject" : {
"type" : "string",
"value" : "statusValue"
},
"thirdObject" : {
"type" : "map",
"value" : {
"customerName" : {
"type" : "string",
"value" : "customerValue"
}
}
},
"fourthObject" : {
"type" : "map",
"value" : {
"firstObject" : {
"type" : "map",
"value" : {
"anotherObj1" : {
"type" : "string",
"value" : "TEST"
},
"anotherObj2" : {
"type" : "date",
"value" : "01/12/2018"
},
"anotherObj3" : {
"type" : "date",
"value" : "31/01/2018"
}
}
}
}
}
}
And toString representation:
Root{firstObject=StringValue{value=productValue}, secondObject=StringValue{value=statusValue}, thirdObject=MapValue{value={customerName=StringValue{value=customerValue}}}, fourthObject=MapValue{value={firstObject=MapValue{value={anotherObj1=StringValue{value=TEST}, anotherObj2=DateValue{value=01/12/2018}, anotherObj3=DateValue{value=31/01/2018}}}}}}
You can easily manipulate output by adding/removing any kind of HasValue instance.
For more info see:
Jackson annotations
Jackson Maven
Download the JSON jar from here. From the client side convert your JSON to string using json.stringify (because JSONObject's constructor accepts only string). After receiving the request from the client do this:
public void doPost(request,response) throws ParseException, JSONException {
parseMapFromJSON(request.getParameter("JSONFromClient"));
}
private void parseMapFromJSON(String JSONParam) throws JSONException
{
JSONObject requestJSON = new JSONObject(JSONParam);
for(int i=0; i<requestJSON.length();i++)
{
String key = (String) requestJSON.names().get(i);
if(key.endsWith("Object"))
{
parseMapFromJSON(requestJSON.get(key).toString());
}
else if(key.startsWith("type") && (requestJSON.get(key).equals("date") || requestJSON.get(key).equals("string")))
{
System.out.println(requestJSON.get("value"));
break;
}
else if(key.startsWith("type") && requestJSON.get(key).equals("map"))
{
parseMapFromJSON(requestJSON.get("value").toString());
}
else if(!key.equals("value"))
{
parseMapFromJSON(requestJSON.get(key).toString());
}
}
}
I'm trying to transform the following JSON into a java Object.
{
"Data":[
{
"AccountId":"2009852923",
"Currency":"EUR",
"Nickname":"SA 01",
"Account":{
"SchemeName":"BBAN",
"Name":"SA 01",
"Identification":"2009852923"
},
"Servicer":{
"SchemeName":"BICFI",
"Identification":"FNBSZAJJ"
}
},
{
"AccountId":"1028232942",
"Currency":"EUR",
"Nickname":"FNBCREDIT",
"Account":{
"SchemeName":"BBAN",
"Name":"FNBCREDIT",
"Identification":"1028232942"
},
"Servicer":{
"SchemeName":"BICFI",
"Identification":"FNBSZAJJ"
}
}
],
"Links":{
"self":"http://localhost:3000/api/open-banking/accounts/1009427721/transactions"
},
"Meta":{
"total-pages":1
}
}
Using the following DTO (for brevity, the referenced classes haven't been posted).
public class TransactionDTO {
private Data[] data;
private Links links;
private Meta meta;
public Data[] getData () { return data; }
public void setData (Data[] data) { this.data = data; }
public Links getLinks () { return links; }
public void setLinks (Links links) { this.links = links; }
public Meta getMeta () { return meta; }
public void setMeta (Meta meta) { this.meta = meta; }
}
The code to transform the DTO to a Java object being:
private TransactionDTO marshall(String accountTransactionsJSON) {
ObjectMapper objectMapper = new ObjectMapper();
TransactionDTO transactionDTO = null;
try {
transactionDTO = objectMapper.readValue(accountTransactionsJSON, TransactionDTO.class);
} catch (IOException e) {
e.printStackTrace();
}
return transactionDTO;
}
I'm getting this error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Data" (class xxx.dto.TransactionDTO), not marked as ignorable (3 known properties: "links", "data", "meta"])
at [Source: java.io.StringReader#48f43b70; line: 2, column: 11] (through reference chain: xxx.dto.TransactionDTO["Data"])
I tried different approach to solve this issue such as:
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
As well as:
#JsonRootName(value = "data")
But I either get the same problem, or no problems, but the TransactionDTO containing null values only.
I guess the problem is the Data field, but I don't know how to fix this problem (the solutions here don't work for me neither).
Questions
Any idea how to fix this problem ?
Should the accessors case reflect the case in the JSON ?
I solved a similar problem using this aproach
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Jackson is case sensitive by default. Try this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
The problem is that your JSON property names (e.g. "Data")
don't match your Java property names (e.g. data).
Besides #psmagin's answer there are two alternative options to fix it:
Keep your Java code unchanged.
And in the JSON contents change all keys (the strings left from the :)
from first-uppercase to first-lowercase:
{
"data":[
{
"accountId":"2009852923",
"currency":"EUR",
"nickname":"SA 01",
"account":{
"schemeName":"BBAN",
"name":"SA 01",
"identification":"2009852923"
},
....
}
Keep the JSON contents unchanged.
And in your Java-code use #JsonProperty annotations
to tell Jackson the corresponding JSON property-names of your Java properties:
public class TransactionDTO {
private #JsonProperty("Data") Data[] data;
private #JsonProperty("Links") Links links;
private #JsonProperty("Meta") Meta meta;
public Data[] getData () { return data; }
public void setData (Data[] data) { this.data = data; }
public Links getLinks () { return links; }
public void setLinks (Links links) { this.links = links; }
public Meta getMeta () { return meta; }
public void setMeta (Meta meta) { this.meta = meta; }
}
and in the same manner in your other Java classes
(Links, Meta, Data, ...)
I would prefer the first option, because property names with first-lowercase
are the established best practice in JSON and Java.
I got this error as I did not intend to map all the JSON fields to my POJO, but only a few. Consequently, it was asking me to mark them ignore. Following sample presents the idea:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
#JsonProperty("kind")
private String kind;
#JsonProperty("id")
private String id;
#JsonProperty("volumeInfo")
private BookInfo bookInfo;
#Override
public String toString() {
return "ClassPojo [kind = " + kind + ", id = " + id + ", bookInfo = " + bookInfo +"]";
}
On the other hand, my Json response carried out 10+ fields.
I have a JSON file (that I have no control over) that looks like this:
{
"some-identifier": {
"#class": "some-prefix.ClassA",
"<classA-property1>": "value1",
"<classA-property2>": "value2",
},
"some-other-identifier": {
"#class": "some-other-prefix.ClassB",
"<classB-property1>": <... possibly nested objects ...>
},
<...>
}
(The classA-properties and classB-properties are the actual names of the members of ClassA and ClassB respectively.)
I would like to deserialize this into a HashMap (mapping each identifier to the actual object) and I want to use a custom TypeIdResolver to determine the actual class to be instantiated (which I can determine from the prefix and class name). The objects themselves should then be deserialized using the default deserializer.
After a lot of searching I couldn't make this work. I need some way to annotate the HashMap in order to set JsonTypeInfo and JsonTypeIdResolver for its content. All examples I've seen so far have those annotations on a base type that all subclasses extend from. However, in my case, there is no common parent class for the classes contained in the JSON (except Object of course). I thought about annotating Object itself with a mixin, but even then this would break default deserialization for the contained objects since it would then expect an #class property on all child objects.
Is there a solution for this scenario?
I think you can accomplish this by enabling the default type information for the object mapper like this:
new ObjectMapper().enableDefaultTyping(
ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT,
JsonTypeInfo.As.PROPERTY);
Here is a complete example:
public class JacksonDefaultTypeInfo {
static class Bean1 {
public String value;
Bean1() {}
public Bean1(final String value) {
this.value = value;
}
#Override
public String toString() {
return "Bean1{" +
"value='" + value + '\'' +
'}';
}
}
static class Bean2 {
public int number;
Bean2() {}
Bean2(final int number) {
this.number = number;
}
}
public static void main(String[] args) throws IOException {
final Map<String, Object> map = new HashMap<>();
map.put("bean1", new Bean1("string"));
map.put("bean2", new Bean2(123));
final ObjectMapper objectMapper = new ObjectMapper()
.enableDefaultTyping(
ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT,
JsonTypeInfo.As.PROPERTY);
final String json = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
System.out.println(json);
final Map<String, Object> result = objectMapper.readValue(
json,
new TypeReference<Map<String, Object>>() {});
System.out.println(result);
}
}
Output:
{
"bean1" : {
"#class" : "stackoverflow.JacksonDefaultTypeInfo$Bean1",
"value" : "string"
},
"bean2" : {
"#class" : "stackoverflow.JacksonDefaultTypeInfo$Bean2",
"number" : 123
}
}
{bean1=Bean1{value='string'}, bean2=Bean2{number=123}}
I'm trying to parse some JSON data using gson in Java that has the following structure but by looking at examples online, I cannot find anything that does the job.
Would anyone be able to assist?
{
"data":{
"id":[
{
"stuff":{
},
"values":[
[
123,
456
],
[
123,
456
],
[
123,
456
],
],
"otherStuff":"blah"
}
]
}
}
You just need to create a Java class structure that represents the data in your JSON. In order to do that, I suggest you to copy your JSON into this online JSON Viewer and you'll see the structure of your JSON much clearer...
Basically you need these classes (pseudo-code):
class Response
Data data
class Data
List<ID> id
class ID
Stuff stuff
List<List<Integer>> values
String otherStuff
Note that attribute names in your classes must match the names of your JSON fields! You may add more attributes and classes according to your actual JSON structure... Also note that you need getters and setters for all your attributes!
Finally, you just need to parse the JSON into your Java class structure with:
Gson gson = new Gson();
Response response = gson.fromJson(yourJsonString, Response.class);
And that's it! Now you can access all your data within the response object using the getters and setters...
For example, in order to access the first value 456, you'll need to do:
int value = response.getData().getId().get(0).getValues().get(0).get(1);
Depending on what you are trying to do. You could just setup a POJO heirarchy that matches your json as seen here (Preferred method). Or, you could provide a custom deserializer. I only dealt with the id data as I assumed it was the tricky implementation in question. Just step through the json using the gson types, and build up the data you are trying to represent. The Data and Id classes are just pojos composed of and reflecting the properties in the original json string.
public class MyDeserializer implements JsonDeserializer<Data>
{
#Override
public Data deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
final Gson gson = new Gson();
final JsonObject obj = je.getAsJsonObject(); //our original full json string
final JsonElement dataElement = obj.get("data");
final JsonElement idElement = dataElement.getAsJsonObject().get("id");
final JsonArray idArray = idElement.getAsJsonArray();
final List<Id> parsedData = new ArrayList<>();
for (Object object : idArray)
{
final JsonObject jsonObject = (JsonObject) object;
//can pass this into constructor of Id or through a setter
final JsonObject stuff = jsonObject.get("stuff").getAsJsonObject();
final JsonArray valuesArray = jsonObject.getAsJsonArray("values");
final Id id = new Id();
for (Object value : valuesArray)
{
final JsonArray nestedArray = (JsonArray)value;
final Integer[] nest = gson.fromJson(nestedArray, Integer[].class);
id.addNestedValues(nest);
}
parsedData.add(id);
}
return new Data(parsedData);
}
}
Test:
#Test
public void testMethod1()
{
final String values = "[[123, 456], [987, 654]]";
final String id = "[ {stuff: { }, values: " + values + ", otherstuff: 'stuff2' }]";
final String jsonString = "{data: {id:" + id + "}}";
System.out.println(jsonString);
final Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, new MyDeserializer()).create();
System.out.println(gson.fromJson(jsonString, Data.class));
}
Result:
Data{ids=[Id {nestedList=[[123, 456], [987, 654]]}]}
POJO:
public class Data
{
private List<Id> ids;
public Data(List<Id> ids)
{
this.ids = ids;
}
#Override
public String toString()
{
return "Data{" + "ids=" + ids + '}';
}
}
public class Id
{
private List<Integer[]> nestedList;
public Id()
{
nestedList = new ArrayList<>();
}
public void addNestedValues(final Integer[] values)
{
nestedList.add(values);
}
#Override
public String toString()
{
final List<String> formattedOutput = new ArrayList();
for (Integer[] integers : nestedList)
{
formattedOutput.add(Arrays.asList(integers).toString());
}
return "Id {" + "nestedList=" + formattedOutput + '}';
}
}
I'm working on a project that communicates with an API using JSON. This is my first attempt at JSON and I've been away from java for a few/several years, so please bear with me.
Here is an idea of what the data looks like:
String 1:
[{
"apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
String 2:
[{
"apicall2":
[{
"thatField":"thatFieldData",
"someFieldsAreTheSame":"someFieldsAreTheSameData",
"otherFieldsAreNotTheSame":"otherFieldsAreNotTheSame"
}]
}]
As you can see from my data example, the API returns a JSON string that contains the api used. The array inside contains the data. The API's have a lot of data fields in common but they are unrelated beyond that.
EDIT: There are dozens of these API's types that will need to be handled.
What I am trying to do is create a response class that accepts all of the JSON strings and returns an object containing the appropriate data.
For Example:
Gson gson = new Gson(); //Custom TypeAdapter goes here if needed.
Response apicall2 = gson.fromJson(apicall2String, Response.class);
System.out.println(apicall2.thatField); //Prints thatFieldData
System.out.println(apicall2.someFieldsAreTheSame); //Prints someFieldsAreTheSameData
System.out.println(apicall2.otherFieldsAreNotTheSame); //Prints otherFieldsAreNotTheSameData
This is where I am lost. Here is what I have so far. I think I need to use a TypeAdapter here but haven't been able to figure how to apply that to my case.
public class Response { //Change to TypeAdapter possibly?
}
public class apicall1 {
String thisField;
String thatField;
String anotherField;
}
public class apicall2 {
String thatField;
String someFieldsAreTheSame;
String otherFieldsAreNotTheSame;
}
You can use Gson's TypeToken class to deserialize json into object. Below is an example:
JSON:
[{ "apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
Model:
class Response{
private List<Result> apicall1;
class Result{
private String thisField;
private String thatField;
private String anotherField;
public String getThisField() {
return thisField;
}
public void setThisField(String thisField) {
this.thisField = thisField;
}
public String getThatField() {
return thatField;
}
public void setThatField(String thatField) {
this.thatField = thatField;
}
public String getAnotherField() {
return anotherField;
}
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
}
public List<Result> getApicall1() {
return apicall1;
}
public void setApicall1(List<Result> apicall1) {
this.apicall1 = apicall1;
}
}
Converter:
public static void main(String[] args) {
String response = "[{ \"apicall1\": [{ \"thisField\":\"thisFieldData\", \"thatField\":\"thatFieldData\", \"anotherField\":\"anotherFieldData\" }]}]";
Gson gson = new Gson();
List<Response> responses = gson.fromJson(response, new TypeToken<List<Response>>(){}.getType());
System.out.println(responses.get(0).getApicall1().get(0).getThisField());
}
I don't know if you want both adapters in one class. Might not be the best OOP design.
To achieve it you would need to do something like so:
public class DoublyTypeAdapter implements JsonDeserializer<ApiCallTypeParent>
{
Gson gson = new Gson();
#Override
public ApiCallTypeParent deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject json = jsonElement.getAsJsonObject();
ApiCallTypeParent desrializeIntoMe;
// Detect which type to implement
if(apiTypeOne(type) {
desrializeIntoMe = new TypeOne();
} else {
desrializeIntoMe = new TypeTwo();
}
for (Map.Entry<String, JsonElement> entry : json.entrySet())
{
switch(entry.getKey()){
case "thisField":
desrializeIntoMe.setThisField(entry.getValue().getAsString());
break;
......
default: // We don't care
break;
}
}
return desrializeIntoMe ;
}
}