How to deserialize a multi-shape JSON response with GSON and Retrofit2? - java

I have a restApi which its response can be something like this (if there is only one object to return):
{
"keys":
{
"id":0,
"name":"john",
"pItems":12
}
}
or like this if there is more:
{
"keys":
[
{
"id":0,
"name":"john",
"pItems":12
},
{
"id":0,
"name":"john",
"pItems":12
}
]
}
When I use a list for Model object, the first case doesn't work.
How can I deserialize it using Gson and Retrofit2?

Ok you cannot change the design of the response, but are you sure you only can receive those responses? if is like this maybe you can create something like this:
class Response{
public Object keys
}
class UserResponse{
public int id;
public String name;
public int pItems;
}
So you going to have two cases:
you can receive an UserResponse object
you can receive a List of UserResponse object
Then to validate if is a List of UserResponse, can be something like this:
if (keys instanceof List<?>){
// then keys is a list
}else{
// then keys is a single object UserResponse
}

You can check if keys element is array or not and then deserialize accordingly.
Assuming, your Model class is UserWrapper.java
import java.util.List;
public class UserWrapper {
private List<User> keys;
public List<User> getKeys() {
return keys;
}
public void setKeys(List<User> keys) {
this.keys = keys;
}
}
User.java is class corresponding to each element of keys attribute.
public class User {
private int id;
private String name;
private int pitems;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPitems() {
return pitems;
}
public void setPitems(int pitems) {
this.pitems = pitems;
}
}
Code to deserialize based on type of keys
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
public class GsonMain{
private static String json = "YOUR JSON";
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
JsonElement keyselement = jsonElement.getAsJsonObject().get("keys");
UserWrapper userWrapper = new UserWrapper();
if (keyselement.isJsonObject()) {
userWrapper.setKeys(new ArrayList<User>());
User user = gson.fromJson(keyselement, User.class);
userWrapper.getKeys().add(user);
} else if (keyselement.isJsonArray()) {
List<User> users = gson.fromJson(keyselement, new TypeToken<List<User>>() {
}.getType());
userWrapper.setKeys(users);
}
userWrapper.getKeys().forEach(user -> System.out.println(user.getName()));
}
}

Related

com.google.gson.stream.MalformedJsonException error: How to solve it?

I want to get the country details from an external api and using Gson to set the data received from the get request to class Country. The problem is that in the response, the currencies key has value which is between [](please see below) and in some cases there is a space between the currencies name values which causes the following error
com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 41 path $.currencies[0].name:
"currencies":[{"code":"BGN","name":"Bulgarian lev","symbol":"лв"}]
#RestController
public class CountryController {
#Autowired
private RestTemplate restTemplate;
private static String baseURL = "https://restcountries.com/v2/";
public Object[] getCountryDetails(String countryName){
Object[] countryDetails = restTemplate.getForObject(baseURL+"name/"+countryName+"?fields=name,alpha2Code,alpha3Code,capital,currencies", Object[].class);
return countryDetails;
}
public Country createCountryObject(String countryName) {
String response = Arrays.asList(getCountryDetails(countryName)).get(0).toString();
Gson g = new Gson();
JsonReader reader = new JsonReader(new StringReader(response.trim()));
reader.setLenient(true);
Country country = g.fromJson(reader, Country.class);
return country;
}
#GetMapping("/")
public String getAll(){
Country country = createCountryObject("bulgaria");
return country.getName();
}
}
Country.java:
package country.neighbours.tour.models;
import java.util.List;
public class Country {
private String name;
private String alpha2Code;
private String alpha3Code;
private List<String> borders;
private Object[] currencies;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getBorders() {
return borders;
}
public void setBorders(List<String> borders) {
this.borders = borders;
}
public String getAlpha2Code() {
return alpha2Code;
}
public void setAlpha2Code(String alpha2Code) {
this.alpha2Code = alpha2Code;
}
public String getAlpha3Code() {
return alpha3Code;
}
public void setAlpha3Code(String alpha3Code) {
this.alpha3Code = alpha3Code;
}
public Object[] getCurrencies() {
return currencies;
}
public void setCurrencies(Object[] currencies) {
this.currencies = currencies;
}
}
How can I get only the currency code?
It looks like you are parsing the response twice; once with restTemplate.getForObject, then you convert its result to a String (the result of your toString() call is most likely not JSON) and then you try to parse it a second time with Gson.
In case you only want to use Gson, you can use a TypeToken in the fromJson call to parse the response JSON array:
List<Country> countries = gson.fromJson(..., new TypeToken<List<Country>>() {}.getType());
Maybe someone more familiar with Spring can also explain how to use only RestTemplate.getForObject for this instead of Gson.

Parse JSON String using ObjectMapper java API

I have a JSON as below. The goal is to get the corresponding "ip","PRODUCTTYPE" and "ID" values.
{
"customerId": "dummy1",
"nameIdmap": {
"10.2.1.0": "{PRODUCTTYPE=null, ID=123}",
"10.2.1.3": "{PRODUCTTYPE=null, ID=456}",
"10.2.1.4": "{PRODUCTTYPE=null, ID=789}",
"10.2.1.5": "{PRODUCTTYPE=null, ID=193}"
}
}
I am using the ObjectMapper API to parse and fetch the values.
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(stringToBeParsed);
String customerID = node.get("customerId").asText();
System.out.println("The Customer ID is ::: "+customerID);
JsonNode nameIdmap = node.get("nameIdmap");
StreamSupport.stream(nameIdmap.spliterator(), false).forEach(
kv -> {
System.out.println(kv.asText().split(",")[0] +" ,
"+kv.asText().split(",")[1]);
});
But the issue is I, am unable to get the key which is the ip-address in this case. Tried different ways to achieve but could not get what i want.
I checked if the nameIdmap is an array nameIdmap.isArray() but it is false.
I also tried below but could not get the ip i.e the key
JsonNode nameIdmap = node.get("nameIdmap");
StreamSupport.stream(nameIdmap.spliterator(), false).collect(Collectors.toList())
.forEach(item -> {
System.out.println(item.asText());
});;
You can try Custom Deserializer as below
1. Create Item class
This is a POJO which stands for an ID and a map of String and IPItem
public class SOItem {
#Override
public String toString() {
return "SOItem [id=" + id + ", map=" + map + "]";
}
String id;
Map<String, SOIPItem> map = new HashMap();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Map<String, SOIPItem> getMap() {
return map;
}
public void setMap(Map<String, SOIPItem> map) {
this.map = map;
}
}
2. Create IPItem class
This is a POJO for an ID and ProductType
public class SOIPItem {
private String type;
private String id;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public String toString() {
return "SOIPItem [type=" + type + ", id=" + id + "]";
}
public SOIPItem(String type, String id) {
super();
this.type = type;
this.id = id;
}
}
3. Create a Custom Deserializer
import java.io.IOException;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class SOCustDeser extends StdDeserializer<SOItem> {
public SOCustDeser() {
this(null);
}
public SOCustDeser(Class<?> vc) {
super(vc);
}
/**
*
*/
private static final long serialVersionUID = -394222274225082713L;
#Override
public SOItem deserialize(JsonParser parser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
SOItem soItem = new SOItem();
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
soItem.setId(node.get("customerId").asText());
JsonNode idmap = node.get("nameIdmap");
Iterator<String> fieldNames = idmap.fieldNames();
while(fieldNames.hasNext()) {
String ip = fieldNames.next();
String textValue = idmap.get(ip).asText();
Pattern p = Pattern.compile("(.*?)=(.*?),(.*?)(\\d+)");
Matcher m = p.matcher(textValue);
if (m.find()) {
soItem.map.put(ip, new SOIPItem(m.group(2), m.group(4)));
}
}
return soItem;
}
}
4. Test class
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MicsTest {
public static void main(String[] args) throws Exception {
ObjectMapper om = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(SOItem.class, new SOCustDeser());
om.registerModule(sm);
SOItem item = om.readValue(new File("c:\\temp\\test.json"), SOItem.class);
System.out.println(item);
}
}
5. Output
SOItem [id=dummy1, map={10.2.1.0=SOIPItem [type=null, id=123], 10.2.1.3=SOIPItem [type=null, id=456], 10.2.1.5=SOIPItem [type=null, id=193], 10.2.1.4=SOIPItem [type=null, id=789]}]
You can get the field names by nameIdmap.getFieldNames as an iterator. You can then iterate over like that:
...
Iterator<String> fieldNames = idmap.getFieldNames();
while(fieldNames.hasNext()) {
String ip = fieldNames.next();
String textValue = idmap.get(ip).getTextValue()
System.out.println(ip + ":" + textValue);
}
If the nested information is also JSON you can then access it further via idmap.get(ip).get("ID"); if not then you still have the option to find it by regex like that:
Pattern p = Pattern.compile("ID=(\\d+)");
Matcher m = p.matcher(textValue);
if(m.find()) {
System.out.println(ip + ":" + m.group(1));
}
Best way to handle these scenarios is to create a matching pojo for your json. This way it gives you flexibility to play around with the data.
Create classes like these
public class Someclass {
private String customerId;
Map<String, String> nameIdmap;
public Map<String, String> getNameIdmap() {
return nameIdmap;
}
public void setNameIdmap(Map<String, String> nameIdmap) {
this.nameIdmap = nameIdmap;
}
public Someclass() {
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
}
And this code will translate your json to SomeClass class
String json = "<copy paste your json here>";
Someclass someclass = objectMapper.readValue(json, Someclass.class);
String s = someclass.getNameIdmap().get("10.2.1.0");
String[] splits = s.split(" ");
String productType = splits[0].split("=")[1];
String id = splits[1].split("=")[1];
System.out.println(productType + " " + id);

Mapping json to Object

I am trying to map a json response that looks something like this
{
"0" : "name",
"1" : "school",
"2" : "hobby",
"3" : "bank",
"4" : "games"
}
The json response is dyanamic and can include other fields depending on how its called so i cant use something like
public class InfoWareAPIResponse {
private String name;
private String school;
//getters and setters
}
Please how can i create a class that i can map such json object to??
you can use a java pojo like this.
package com.something;
import com.fasterxml.jackson.annotation.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({})
public class InfoUnAwareAPIResponse {
#JsonIgnore
#Valid
private Map<String, Object> additionalProperties = new HashMap();
public InfoUnAwareAPIResponse() {
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
public int hashCode() {
return (new HashCodeBuilder()).append(this.additionalProperties).toHashCode();
}
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (!(other instanceof InfoUnAwareAPIResponse)) {
return false;
} else {
InfoUnAwareAPIResponse rhs = (InfoUnAwareAPIResponse) other;
return (new EqualsBuilder()).append(this.additionalProperties, rhs.additionalProperties).isEquals();
}
}
}
And marshel string like this
public static void main(String args[]) throws IOException {
InfoUnAwareAPIResponse in = mapJsonToObject("{\"hello\":\"world\"}", InfoUnAwareAPIResponse.class);
System.out.print("" + in.toString());
}
public static <T> T mapJsonToObject(String input, Class<T> clazz) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
T requestedClass = objectMapper.readValue(input, clazz);
return requestedClass;
}
I ran the above code and it works fine for me.

JAVA parsing with GSON multiple arrays

I've looked all over and found some good answers to my question but i can't get it to work. I found this thread (Parsing single json entry to multiple objects with Gson) but i don't understand where my problem is.
I want to read the file into new Objects (if it's possible with just one then even better but i couldn't find out how).
First the int threads and then the array of tools(where each tool is an Object)
This is my TXT file :
{
"threads": 4,
"tools": [
{
"tool": "gs-driver",
"qty": 35
},
{
"tool": "np-hammer",
"qty": 17
},
{
"tool": "rs-pliers",
"qty": 23
}
]
}
this is my deseralization class, and two of my object classes
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
public class Deserializer implements JsonDeserializer<ParseJson> {
public ParseJson deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
ParseJson test = new ParseJson();
test.setThreads(obj.get("threads").getAsInt());
Gson toolsGson = new Gson();
Type toolsType = new TypeToken<List<ParseTool>>(){}.getType();
List<ParseTool> toolsList = toolsGson.fromJson(obj.get("tools"), toolsType);
test.setTools(toolsList);
return test;
}
}
import java.util.List;
public class ParseJson {
private int threads;
private List<ParseTool> tools;
public void setThreads(int _threads) {
this.threads = _threads;
}
public int getThreads() {
return threads;
}
public void setTools(List<ParseTool> tools) {
this.tools = tools;
}
public List<ParseTool> getTools() {
return tools;
}
}
public class ParseTool {
private int qty;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
}
I can get the "threads" but it doesn't parse the array for some reason.
Thanks,
ParseTool contains a propery named name, but the JSON indicates that it's named tool.
You should therefore rename the property name to tool:
public class ParseTool {
private int qty;
private String tool;
public String getTool() {
return tool;
}
public void setTool(String tool) {
this.tool = tool;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
}

Java ; JSON ; Jackson ; Retrieving the value from a List inside an Array from JSON

I'm trying to read the values from a JSON URL, however I don't know how I can proceed with reading the values from a List inside of an Array? Below you will find my POJO, Main, and JSON code. Thank you so much for your help
POJO:
package org.jcexchange.FBApp;
import java.util.List;
import org.jcexchange.FBApp.Details;
public class Users {
private List<Details> Values;
public List<Details> getValues() {
return this.Values;
}
public void setValues(List<Details> Values) {
this.Values = Values;
}
}
public class Details {
private String user_name;
private String user_password;
private int age;
private String user_email;
public String getUserName() {
return this.user_name;
}
public void setUserName(String user_name) {
this.user_name = user_name;
}
public String getUserPassword() {
return this.user_password;
}
public void setUserPassword(String user_password) {
this.user_password = user_password;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getUserEmail() {
return this.user_email;
}
public void setUserEmail(String user_email) {
this.user_email = user_email;
}
}
Main:
public class Main {
public static void main(String[] args) {
try {
URL jsonURL = new URL("https://jchtest.herokuapp.com/index.php?
PW=2");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
Users[] a1 = mapper.readValue(jsonURL, Users[].class);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
I'm able to pull the JSON from a webservice, however I'm stuck trying to figure out how I could retrieve for instance the user_name from the first "Values" index of the JSON array
JSON:
[
{
"Values": {
"user_name": "jhart",
"user_password": "gooddeval1",
"age": 28,
"user_email": "heheh"
}
},
{
"Values": {
"user_name": "bdole",
"user_password": "Passwordd",
"age": 82,
"user_email": "hahah"
}
}
]
Well , it is a little confusing here may be because i dont have the full context. From the de-serializer you are telling me that i expect an Array of Users and then within each User i have a List of "Values" , but the JSON tells me that Values is a singular property for Users. Anyways , here is a sample that works on the assumption i have made. This can be fiddled around to change the collection and singular properties
import org.codehaus.jackson.annotate.JsonProperty;
public class Users {
#JsonProperty("Values")
private Details Values;
public Details getValues() {
return this.Values;
}
public void setValues(Details Values) {
this.Values = Values;
}
}
import org.codehaus.jackson.annotate.JsonProperty;
public class Details {
#JsonProperty("user_name")
private String user_name;
#JsonProperty("user_password")
private String user_password;
#JsonProperty("age")
private int age;
#JsonProperty("user_email")
private String user_email;
public String getUserName() {
return this.user_name;
}
public void setUserName(String user_name) {
this.user_name = user_name;
}
public String getUserPassword() {
return this.user_password;
}
public void setUserPassword(String user_password) {
this.user_password = user_password;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getUserEmail() {
return this.user_email;
}
public void setUserEmail(String user_email) {
this.user_email = user_email;
}
}
import java.net.URL;
import org.codehaus.jackson.map.ObjectMapper;
public class Main {
public static void main(String[] args) {
try {
URL jsonURL = new URL("https://jchtest.herokuapp.com/index.php?PW=2");
ObjectMapper mapper = new ObjectMapper();
Users[] a1 = mapper.readValue(jsonURL, Users[].class);
System.out.println(a1[0].getValues().getUserName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
This prints "jhart" for me.
Please note : One thing you can try is based on the array/singular property you can populate the Object and write it as JSON. That way you can find what is different in what Jackson Deserializer expects vs What we are actually supplying.

Categories