How to Parse Nested / Multiple Json Objects using Retrofit - java

The JSON I'm parsing looks like this:
{ "version": 1
"data": {
"1001": {
"id": 1001,
"name": "herp",
"into": [
"3111": "we"
]
},
"1032": {
"id": 1002,
"name": "derp",
"into": [
"36": "w",
"12341: "c"
],
"tags": [
"hi there"
],
"cost" {
"even": 15
}
},
"1603": {
"id": 1003,
"name": "her",
"into": [
"37": "dll",
"58": "eow",
"32145": "3a"
],
"cost" {
"highest": 325
"lowest": 100
}
},
.... Even more data
}
The Json that is within "data" goes on for a while and does not have a set endpoint. I have no control over the Json, I'm just trying to read it. Unfortunately, with my class code I'm unable to get it to work. When I make a retrofit call the information inside "data" is empty.
I've tried many iterations of implementing this, including using a deserializer and restructuring my POJO code. This is the current state of my Data class:
public class Data {
private Map<String, Item> itemData;
// Relevant Getters, Setters and Constructors //
}
For my Item Class, the main issue is that the JSON Content isn't set, it can vary at times. As you can see above the values inside "into" vary and sometimes the amount of things within item changes as well such as "tags" or "cost":
public class Item {
private int id;
private String name;
private String group;
private String description;
private Map<String, String> into;
private List<String> tags;
private Map<String, Integer> cost;
// Relevant Getters, Setters and Constructors //
When I use this code my data class is empty, I don't see any errors in the log so I can't seem to figure out why the GSON isn't working with this.
In case you wanted to see how I construct my RestClient, here it is:
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint(ROOT)
.setClient(new OkClient(new OkHttpClient()))
.setLogLevel(RestAdapter.LogLevel.FULL);
RestAdapter restAdapter = builder.build();
REST_CLIENT = restAdapter.create(DataApi.class);
I know that my Rest Query works because I can get the content within "version" but everything inside data is null.

I am officially a dumb scrub and completely looked over the easiest possible fix and should have my account and developer title revoked.
This is all I should have done:
public class ItemGroup {
private String version;
private Map<String,Item> data;
//...Man i'm so dumb...
}
AS REFERENCE FOR THE FUTURE. The reason why this works is because the JSON is in this format { { } { } { } }. Which means you have 3 objects of objects, as opposed to { [ ] [ ] [ ] } which is 3 objects of a list. What I had done was treat { { } { } { } } as { { { } { } { } } }. Which is not correct. By using a map which is basically a collection of pairs, we are able to imitate the { { } { } { } } with a Map.
Map Object { {Key-Pair Object} {Key-Pair Object} {Key-Pair Object} }

Just a quick idea for your Pojos:
Not sure if this will work. Maybe write a custom deserializer.
public class YourResponse {
int version;
Data data;
class Data {
Subdata subData;
}
class Subdata {
private int id;
private String name;
private ArrayList<Into> into;
private ArrayList<String> tags;
//...
}
class Into {
// "3111": "we"
private String into;
}
}

Related

Deserialization with JSON objects which fields can be of different types

I have the following json file:
{
"authors":
[{"id":"author7",
"book":[
[
{
"value":{"pages":123}}]]},
{
"id": "author3",
"book": [
[
{
"value": {
"title": "LOTR"
}
},
{
"value": {
"boolean": false
}
},
],
[
{
"value": {
"pages": 350
}
},
{
"value": {
"boolean": false
}
},
],
[
{
"value": {
"boolean": false
}
},
{
"value": {
"pages": 150
}
},
]
]
},
}
I want to be able to create an object of Author but I am having problems while mapping the Json file with the Java classes I have created.
I understand that, while mapping the json file with the java classes, Authors class should have as fields
public class Authors{
private String authorId;
private Book book;
}
Class Book should be like this
public class Book {
private List<Values> values
public Book() {
}
}
But what about class Values?
public class Values{
private int pages;
private Boolean bool;
private String title;
public Values() {
}
}
Is this the correct way to map it? Because I see that if I create an object of Values it will ask me to modify the constructor or create a new constructor for each different object that comes from Json
Thank you for reading and helping!
Easy way out would to be to define Values by type 'object' as,
public class Book {
private List<Object> values
}
This way you won't run into the issue of having properties with null values when they don't exist in the JSON. Also, the JSON would be entirely parsed into an object even if new properties are get introduced (or which may not be defined under values class, as you have done above causing those not being mapped to the object).
However, when you are using 'object', be mindful with your logic that uses this parsed object. Although, you are assured to access the properties from the object as in the JSON, you will have to conditionally check whether the nessacary property exists first in each logical context to avoid possible undefined value errors.

How to loop trough nested JSON and get value using Java, SpringBoot

I have a nested JSON that I want to loop through and get value based on key.
Data.json
{
"car": [
{
"date": 1324599600000,
"values": [
{
"name": "Audi",
"price": "11212.12"
},
{
"name": "Bmw",
"price": "22321.3"
},
{
"name": "Cittroen",
"price": "23432.2"
},
{
"name": "Tuareg",
"price": "556456.3"
}
]
}
I created 3 object models based on JSON data.
CarResponse.java
public class CarResponse {
#JsonProperty(value = "car")
List<Car> cars;
//getters,setters
Car.java
public class Car{
#JsonProperty("values")
private List<CarValue> carValue;
//getters, setters
CarValue.java
public class CarValue {
private String name;
private BigDecimal price;
//getters, setters
DataParse.java
public class DataParse{
CarResponse response;
public CarValue parse(){
CarValue carValue = new CarValue();
//NEED HELP WITH THIS PART
for(Car cars : response.getQuote()){
for(CarValue qv : cars.getCarValue()){
String type = qv.get("name").asText();
Decimal value = qv.get("price").decimalValue();
carValue.setName(qv.getName());
carValue.setPrice(qv.getPrice());
}
}
return quoteValue;
}
}
In JSON I need to check if car name is Audi and if car name is Tuareg then save it and display it.
Nested for loop part of code is not working
for(Car cars : response.getQuote()){
for(CarValue qv : cars.getCarValue()){
String type = qv.get("name").asText();
Decimal value = qv.get("price").decimalValue();
carValue.setName(qv.getName());
carValue.setPrice(qv.getPrice());
}
}
Just put it as an example of what I need to do in this step which is check for key in JSON and if it is that key with for example name = Audi then
carValue.setName(qv.getName());
With my code above I am getting this in the console.
carValue=[carValue{name=null, price=null}
Assuming that your deserialization is working correctly, you should change the part that "is not working" to:
String type = qv.getName();
BigDecimal value = qv.getPrice();
If for some reason you want to use this qv.get(key) method, you should post its implementation.
When getName and getPrice are still returning null, it means you have a problem with the deserialization, for example a wrong setter method name. Still, without the implementation is impossible to understand.
Here is the official documentation of Jackson, the library used by default with spring-boot for JSON serialization / deserialization.
And here is Spring blog post about it.

How to deserialize a json array followed by a normal property into a pojo in jackson. The array alone works

The difficulty is, that the json labels are partly dynamic in the rest response of the Kraken api. At first I introduce a working case. I connect the Kraken trading api to fetch currencies as assets and got the following result in json.
{
"error": [],
"result": {
"AAVE": {
"aclass": "currency",
"altname": "AAVE",
"decimals": 10,
"display_decimals": 5
},
"ZUSD": {
"aclass": "currency",
"altname": "USD",
"decimals": 4,
"display_decimals": 2
}
}
}
AAVA and ZUSD in this example are dynamic labels. I use the embedded Jackson to parse it in the OpenFeign framework. The result part are covered with the following generic class:
public class Response<T> {
private List<String> error = new ArrayList<>();
private T result;
// getter and setters
}
And as root class for the assets, the dynamic labels AAVA and ZUSD are handled by a Map:
public class AssetInfoResponse extends
Response<Map<String, AssetInfo>> {
}
The pojo AssetInfo:
public class AssetInfo implements Serializable{
#JsonProperty("altname")
private String alternateName;
#JsonProperty("aclass")
private String assetClass;
#JsonProperty("decimals")
private Byte decimals;
#JsonProperty("display_decimals")
private Byte displayDecimals;
// getters, setters ...
}
The above case works perfectly, also the solution with the dynamic labels.
Here is the response with the ohlc data, that looks similar and I have no Idea to solve the deserialization problem in the next case:
{
"error": [],
"result": {
"XXBTZEUR": [
[
1613212500,
"39000.1",
"39010.1",
"38972.3",
"38994.1",
"38998.1",
"3.23811638",
70
],
[
1613212560,
"38994.3",
"39014.5",
"38994.3",
"39014.5",
"38997.3",
"0.95105956",
11
]
],
"last": 1613212500
}
}
The cause of the problem is the "last": 1613212500 property line. When I remove this line, the response can be parsed without problems. I try to solve it with the following classes, Response is the upper described class.
public class OhlcLastResponse<T> extends Response<T> {
private Long last;
// getters and setters
}
The next class extends the prevourious class and is the root class for the objectmapper:
public class OhlcResponse
extends OhlcLastResponse<Map<String, List<Candelstick>>> {
}
And the pojo that holds the candlestick data:
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
#JsonPropertyOrder({ "time", "open", "high", "low",
"close", "vwap", "volume", "count" })
public class Candelstick implements Serializable {
private Integer time;
private BigDecimal open;
private BigDecimal high;
private BigDecimal low;
private BigDecimal close;
private BigDecimal vwap;
private BigDecimal volume;
private Integer count;
// getters and setters ...
}
and here is the error:
"38997.3",
"0.95105956",
11
]
],
"last": 1613212500
}
}
"; line: 26, column: 11] (through reference chain: OhlcResponse["result"]->java.util.LinkedHashMap["last"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
Jackson tries to put the last property into the map, but the map was finished by ],. Line 26 is the line with the
last label in the json file.
Is there a possibility to parse this json? I think it must be possible, because the array is closed by the square bracket.
I hosted the rest client on github. To reproduce the error just clone it and run mvn test.
The solution is a deserializer, because the type handling is very difficult in this case. The deserializer decide between the two cases, array or the single last value, and call in the case of an array the deserializer for the CandleStick Class:
public class OhlcDeserializer extends JsonDeserializer<OhclPayload> {
#Override
public OhclPayload deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<CandleStick> candleSticks = new ArrayList<CandleStick>();
Long last = null;
ObjectCodec objectCodec = p.getCodec();
JsonNode jsonNode = objectCodec.readTree(p);
Iterator<Entry<String, JsonNode>> payloadIterator = jsonNode.fields();
while (payloadIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = payloadIterator.next();
if (entry.getKey().equals("last")) {
last = entry.getValue().asLong();
} else if (entry.getValue().isArray()) {
for (JsonNode node : entry.getValue()) {
CandleStick cs = p.getCodec().treeToValue(node, CandleStick.class);
candleSticks.add(cs);
}
}
}
return new OhclPayload(candleSticks, last);
}
}
I changed the OhclResponse to:
public class OhlcResponse extends Response<OhclPayload> {
}
And insert a OhlcPayload class for the deserializer:
#JsonDeserialize(using = OhlcDeserializer.class)
public class OhclPayload {
private List<CandleStick> candleSticks;
private Long last;
// getters and setters
}
Thats all.

Json Request with POSTMAN

I have this class:
#Getter
#Setter
IntestatarioPrivacySospensiva {
private IntestatarioInput intestatario;
private List<DatiPrivacySospensiva> datiPrivacy;
private String sportelloRiferimento;
private String riferimento;
private String note;
private String accettazioneConsensoC1;
private String accettazioneConsensoC3;
}
I have to make a JSON request to map this object, I have made this but it is never seen as IntestatarioPrivacySospensiva object.
[
{
//this are intestatarioInput variable
"abi":"abi",
"cf":"cf",
"ndg":"ndg"
},
{//This are datiprivacy variable
"tipoConsenso":"tipoConsenso",
"consenso":"consenso"
},
{
"sportelloRiferimento":"sportelloRif",
"riferimento":"riferimento",
"note":"note",
"accettazioneConsensoC1":"true",
"accettazioneConsensoC3":"false"
}
]
My service have in the request ArrayList x; how i have to map it in json?
I'm using Postman to send JSON.
I believe this should work:
[{
"sportelloRiferimento":"sportelloRif",
"riferimento":"riferimento",
"note":"note",
"accettazioneConsensoC1":"true",
"accettazioneConsensoC3":"false",
"intestatario" : {
"abi":"abi",
"cf":"cf",
"ndg":"ndg"
},
"datiPrivacy" : [ {
"tipoConsenso":"tipoConsenso",
"consenso":"consenso"
} ]
},
{
"sportelloRiferimento":"sportelloRif",
"riferimento":"riferimento",
"note":"note",
"accettazioneConsensoC1":"true",
"accettazioneConsensoC3":"false",
"intestatario" : {
"abi":"abi",
"cf":"cf",
"ndg":"ndg"
},
"datiPrivacy" : [ {
"tipoConsenso":"tipoConsenso",
"consenso":"consenso"
} ]
}]
You can use Jackson 2.x to get JSON strings from your Java objects. Checkout the below example:
https://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/

Deserialization of JSON array

I'm not sure how to deserialize array containing plain strings.I'm trying to parse the following JSON
{
"state":"RT",
"testMethod":"electronic",
"testElements":[
{
"testId":[
"UT_ITXref",
"Fed_ITXref"
]
},
"testStartDate",
"testEndDate",
"testDueDate"
]
}
I'm getting the following error:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.test.rules.model.TestElements: no String-argument constructor/factory method to deserialize from String value ('testStartDate')
at [Source: {"state":"RT","testMethod":"electronic","testElements":[{"testId":["UT_ITXref","Fed_ITXref"]},"testStartDate","testEndDate","testDueDate"}]}; line: 1, column: 247] (through reference chain: com.test.rules.model.TestRules["testElements"]->java.lang.Object[][1])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:370)
at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:315)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1282)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:159)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:196)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:20)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:511)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:396)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1198)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1626)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1220)
Here is what I did , I used #JsonCreator annotation to deserialize
public class TestRules {
private String state;
private String testMethod;
private TestElements[] testElements;
#JsonCreator
public TaxRules(
#JsonProperty("state") String state,
#JsonProperty("testMethod") String testMethod,
#JsonProperty("testElements") TestElements[] testElements
) {
this.state = state;
this.testMethod = testMethod;
this.testElements = testElements;
}
}
public class TestElements {
private List<String> testId;
private List<String> elements;
public List<String> getElements() {
return elements;
}
public void setElements(List<String> elements) {
this.elements = elements;
}
public List<String> getTestId() {
return testId;
}
public void setTestId(List<String> testId) {
this.testId = testId;
}
}
Should I write custom deserializer or Is there any way that I can use the jackson API for this. Any suggestions would be appreciated.
Actually errors tells something.
JSON parser found that for testElements property there is an Array of Objects, but your Json file has mixed content.
first element is an object (I assume it is TestElement class). Then parser creates that object with empty constructor and calls appropriate setters for its properties.
but...
second,third and forth elements are String, so error says that parser tries to find constrictor with String as argument.
So, you may try to make that constructor in TestElement class and see will it work or not...
Do not forget to keep empty constructor as well.
I cannot guarantee it will work but, at least error says that.
BTW are you sure your Json object is correct? but not something like that?
{
"state":"RT",
"testMethod":"electronic",
"testElements":[
{
"testId":[
"UT_ITXref",
"Fed_ITXref"
]
}],
"testStartDate":"01-01-2017",
"testEndDate":"01-02-2017",
"testDueDate":"01-03-2017"
}
I'm a little confused because StartDate, EndDate, DueDate semantically look more like test attributes, not as elements in testElements array
{
"state": "RT",
"testMethod": "electronic",
"testElements": [
{
"testId": [
"UT_ITXref", // <-- this object is deserialized just fine
"Fed_ITXref"
]
},
"testStartDate", // <-- this is where the error is happening
"testEndDate",
"testDueDate"
]
}
Did you intend the json to be interpreted as if it looked like the following?
{
"state": "RT",
"testMethod": "electronic",
"testElements": [
{
"testId": [
"UT_ITXref",
"Fed_ITXref"
]
},
{
testId: [
"testStartDate"
]
},
{
testId: [
"testEndDate"
]
},
{
testId: [
"testDueDate"
]
}
]
}
If so, you'll need to make a custom deserializer to detect whether the element in the array is an object or a string. If it's a string, you'll probably want to construct the TestElement yourself.

Categories

Resources