I want to work with crypto-stock data described here in my spring boot application. The RESTTemplate uses Gson for deserialization. Response data looks like:
{
"IOST": {
"EUR": 0.01147,
"USD": 0.01296
},
"XRP": {
"EUR": 0.2837,
"USD": 0.3208
},
...
}
I have already already written a custom deserializer before. The problem is, that this comes as a single object with key-value pairs insted of as an array. The result should be a list of following objects:
public class Symbol {
private Long id; // not relevant during conversion
private Date timestamp; // not relevant during conversion
private String symbol;
private Double eurPrice;
private Double usdPrice;
}
Any idea how this can be accomplished this?
Because response from this API is dynamic and depends from parameters the best choice is to use dynamic structure on deserialisation side. The best choice is Map. As keys you can use String or enum: Currency, Crypto. After deserialisation you can convert Map to required POJO class. Simple example:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GsonTest {
public static void main(String[] args) {
String json = "{\"IOST\": {"
+ " \"EUR\": 0.01147,"
+ " \"USD\": 0.01296"
+ " },"
+ " \"XRP\": {"
+ " \"EUR\": 0.2837,"
+ " \"USD\": 0.3208"
+ "}}";
Gson gson = new GsonBuilder().create();
Type type = new TypeToken<Map<String, Map<Currency, BigDecimal>>>() {
}.getType();
Map<String, Map<Currency, BigDecimal>> response = gson.fromJson(json, type);
List<Symbol> symbols = response.entrySet()
.stream()
.map(e -> {
Symbol symbol = new Symbol();
symbol.setSymbol(e.getKey());
symbol.setEurPrice(
e.getValue().getOrDefault(Currency.EUR, BigDecimal.ZERO).doubleValue());
symbol.setUsdPrice(
e.getValue().getOrDefault(Currency.USD, BigDecimal.ZERO).doubleValue());
return symbol;
}).collect(Collectors.toList());
System.out.println(symbols);
}
enum Currency {
EUR,
USD
}
}
Above example prints:
[Symbol{id=null, timestamp=null, symbol='IOST', eurPrice=0.01147, usdPrice=0.01296}, Symbol{id=null, timestamp=null, symbol='XRP', eurPrice=0.2837, usdPrice=0.3208}]
If you want to create directly List of Symbol-s you need to implement custom deserialiser. For example, take a look on this question: Parsing JSON Array to Java List Using Gson
You have to make a class as below
public class Symbol {
private double EUR;
private double USD;
public double getEUR() {
return EUR;
}
public void setEUR(double EUR) {
this.EUR = EUR;
}
public double getUSD() {
return USD;
}
public void setUSD(double USD) {
this.USD = USD;
}
}
Above is a model and below is parsing
try {
HashMap<String, Symbol> multiMap = new HashMap<String, Symbol>();
JSONObject mJsonObject = new JSONObject(mResponse);
Iterator<String> iter = mJsonObject.keys();
while (iter.hasNext()) {
String key = iter.next();
try {
Symbol msymbol = new Symbol();
msymbol.setEUR(mJsonObject.getJSONObject(key).getDouble("EUR"));
msymbol.setEUR(mJsonObject.getJSONObject(key).getDouble("USD"));
multiMap.put(key, msymbol);
} catch (JSONException e) {
// Something went wrong!
}
}
}catch (Exception e){
e.printStackTrace();
}
Hareshs anweser didn't fit 100% but it helped me figuring it out:
var url = "...";
var entries = new ArrayList<Symbol>();
var timestamp = Timestamp.from(Instant.now());
Symbol symbol;
ResponseEntity<JsonObject> response = restTemplate.getForEntity(url, JsonObject.class);
for (Map.Entry<String, JsonElement> entry : response.getBody().entrySet()) {
symbol = new Symbol();
symbol.setSymbol(entry.getKey());
symbol.setEurPrice(entry.getValue().getAsJsonObject().get("EUR").getAsDouble());
symbol.setUsdPrice(entry.getValue().getAsJsonObject().get("USD").getAsDouble());
symbol.setTimestamp(timestamp);
entries.add(symbol);
}
This does the trick. However, if anybody finds a way to do this completely within a Gson deserializer I would love to hear it!
Related
I am trying to get the rates from https://api.ratesapi.io/api/latest into an ArrayList<Currency> of a custom Currency class:
public class Currency {
private String shortName;
private double rate;
...
}
The JSON looks like:
{"base":"EUR","rates":{"GBP":0.90033,"HKD":9.1786,"IDR":17304.0,
"ILS":4.0309,"DKK":7.45,"INR":88.765,"CHF":1.0759,"MXN":26.615,
"CZK":26.202,"SGD":1.6236,"THB":36.832,"HRK":7.468,"MYR":4.9604,
"NOK":10.6538,"CNY":8.2325,"BGN":1.9558,"PHP":58.136,"SEK":10.3165,
"PLN":4.4073,"ZAR":20.7655,"CAD":1.5748,"ISK":160.2,"BRL":6.334,
"RON":4.836,"NZD":1.7828,"TRY":8.5853,"JPY":124.96,"RUB":86.9321,
"KRW":1404.99,"USD":1.1843,"HUF":346.23,"AUD":1.6492},"date":"2020-08-06"}
Using org.json I managed to get the data into a JSONObject:
JSONObject obj = new JSONObject(getJSON("https://api.ratesapi.io/api/latest"));
As far as I understand, the normal procedure is now to convert the JSONObject into a JSONArray. However trying:
JSONArray jsonArray = obj.getJSONArray("rates");
fails with the error message:
Exception in thread "main" org.json.JSONException: JSONObject["rates"]
is not a JSONArray.
How do I fix this error or is there another way to make an ArrayList out of the JSON?
I suspect that the problem are missing square brackets in the JSON string.
If you take a look at the JSON returned by the API, you get a JSON object:
{"base":"EUR","rates":{"GBP":0.90033,"HKD":9.1786, ... },"date":"2020-08-06"}
You probably want to do something like this:
JSONObject obj = new JSONObject(getJSON("https://api.ratesapi.io/api/latest"));
JSONObject rates = obj.getJSONObject("rates");
final Iterator<String> keys = rates.keys();
while (keys.hasNext()) {
final String key = keys.next();
final Currency currency = new Currency(key, rates.getDouble(key));
// do something with the Currency
}
The object "rates" is not a JSONArray, is a JSONObject.
So you have to do obj.getJSONObject(rates");then iterate on the fields of the JSONObject using map methods (for examply using keySet() )
A working solution using Jackson library and Lombok may be as follows:
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.*;
import java.util.*;
import java.util.stream.Collectors;
public class CcyApiParser {
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public static class Currency {
private String shortName;
private double rate;
}
#Getter
#Setter
public static class RatesApiResponse {
private String base;
private Map<String, Double> rates;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate date;
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule()); // to parse date
URL apiUrl = new URL("https://api.ratesapi.io/api/latest");
// read proper api response
RatesApiResponse rates = mapper.readValue(apiUrl, RatesApiResponse.class);
// convert inner rates into list of Currency objects
List<Currency> ccys = rates.getRates().entrySet().stream()
.map(e -> new Currency(e.getKey(), e.getValue()))
.collect(Collectors.toList());
ccys.forEach(ccy -> System.out.printf("%s=%s%n", ccy.getShortName(), ccy.getRate()));
}
}
Output
GBP=0.90033
HKD=9.1786
IDR=17304.0
ILS=4.0309
... etc.
Update
It is also possible to customize deserialization of RatesApiResponse and move mapping of "rates" into this class to convert immediately into list of currencies.
#Getter
#Setter
public static class RatesApiResponse {
private String base;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private List<Currency> ccys;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate date;
// no getter for rates
// this customized setter for the map of rates converts into a list
#JsonProperty("rates")
public void setRates(Map<String, Double> rates) {
ccys = rates.entrySet().stream()
.map(e -> new Currency(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}
// Updates in the test method
RatesApiResponse rates = mapper.readValue(src, RatesApiResponse.class);
rates.getCcys().forEach(ccy -> System.out.printf("%s=%s%n", ccy.getShortName(), ccy.getRate()));
You can use ObjectMapper class to convert json from some URL to some kind of object. In this case (if json structure is always the same) it can be Map<String, Object>.
ObjectMapper mapper = new ObjectMapper();
URL url = new URL("https://api.ratesapi.io/api/latest");
Map<String, Object> map = mapper.readValue(url, Map.class);
System.out.println(map);
// {base=EUR, rates={GBP=0.90373, HKD=9.1585, ... , AUD=1.6403}, date=2020-08-07}
Then you can get inner rates map, and (if it is needed) convert it to list using java stream api:
Map<String, Double> rates = (Map<String, Double>) map.get("rates");
System.out.println(rates); // {GBP=0.90373, HKD=9.1585, ... , AUD=1.6403}
Convert Map<String, Object> to ArrayList<Currency>:
ArrayList<Currency> list = rates.entrySet().stream()
.map(entry -> new Currency(entry.getKey(), entry.getValue()))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println(list); // [GBP=0.90373, HKD=9.1585, ... , AUD=1.6403]
Note: add a constructor with two fields shortName and rate;
Note: override the toString method as follows: shortName + "=" + rate;
Maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.2</version>
</dependency>
See also: «Formatting Json Response into an Array Java».
Exception in thread "main" org.json.JSONException: JSONObject["rates"]
is not a JSONArray.
You got this error because rates is not in the form of an array. It is simply an element like base and date but looks like an array. Get it from the JSON string like you get base and date from it and then process it to create the required List<Currency>.
Given below is the working code with the explanation added as comments in the code:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;
class Currency {
private String shortName;
private double rate;
public Currency(String shortName, double rate) {
this.shortName = shortName;
this.rate = rate;
}
#Override
public String toString() {
return shortName + ":" + rate;
}
}
public class Main {
public static JSONObject getJSON(String url) throws IOException, JSONException {
// Create a URLConnection for the given URL
URLConnection connection = new URL(url).openConnection();
// Add header to avoid 403 Forbidden HTTP status code
connection.addRequestProperty("User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:79.0) Gecko/20100101 Firefox/79.0" + "");
StringBuilder jsonStr = new StringBuilder();
// Get InputStream from connection and read the response
try (InputStream is = connection.getInputStream();) {
Reader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
int ch;
while ((ch = reader.read()) != -1) {
jsonStr.append((char) ch);
}
}
return new JSONObject(jsonStr.toString());
}
public static void main(String[] args) throws IOException, JSONException {
JSONObject jsonObj = getJSON("https://api.ratesapi.io/api/latest");
// Get rates from jsonObj
String rates = jsonObj.get("rates").toString();
// Remove {, }, and " from the string
String[] keyValArr = rates.replaceAll("[\\{\\\"}]", "").split(",");
// List object to hold Currency objects
List<Currency> list = new ArrayList<>();
for (String keyVal : keyValArr) {
// Split each key:value string on ':'
String[] curRate = keyVal.split(":");
// Add Currency object to List
list.add(new Currency(curRate[0], Double.parseDouble(curRate[1])));
}
// Display list
list.forEach(System.out::println);
}
}
Output:
CHF:1.0804
HRK:7.4595
MXN:26.5127
...
...
...
NZD:1.7786
BRL:6.3274
For example my JSON text is coming like this.
"pages":{"42010":{"pageid":42010,"ns":0,"title":"Queen (band)"}}
Because everytime my json text is coming with different number which is inside pages tag.
How do i convert this to Java equivalent class?
Currently my generated java class is something like this.
#Generated("org.jsonschema2pojo")
public class Pages {
#SerializedName("42010")
#Expose
private _42010 _42010;
}
That _42010 class contains the inner fields like "pageid":42010,"ns":0,"title":"Queen (band)", since i am getting everytime new number inside pages, its not working. its working only for the specific json text.
You can use a custom deserialiser that ignored the changing number. For example:
package jacksonTest;
import java.io.IOException;
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class CustomDeserialiser {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"42010\":{\"pageid\":42010,\"ns\":0,\"title\":\"Queen (band)\"}}";
String json2 = "{\"12345\":{\"pageid\":12345,\"ns\":0,\"title\":\"Queen (band)\"}}";
Gson g = new GsonBuilder().registerTypeAdapter(Pages.class, new PagesDeserialiser()).create();
Pages fromJson = g.fromJson(json, Pages.class);
System.out.println(fromJson);
fromJson = g.fromJson(json2, Pages.class);
System.out.println(fromJson);
}
public static class PagesDeserialiser implements JsonDeserializer<Pages> {
#Override
public Pages deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws com.google.gson.JsonParseException {
JsonObject object = json.getAsJsonObject();
Pages p = new Pages();
object.entrySet().forEach( e -> {
JsonObject tmp = e.getValue().getAsJsonObject();
if(tmp.get("pageid") != null) {
// right object
p._42010 = new _42010();
p._42010.ns = tmp.get("ns").getAsInt();
p._42010.pageid = tmp.get("pageid").getAsInt();
p._42010.title = tmp.get("title").getAsString();
}
});
return p;
}
}
public static class Pages {
_42010 _42010;
#Override
public String toString() {
return _42010.toString();
}
}
public static class _42010 {
int pageid;
int ns;
String title;
#Override
public String toString() {
return title + " " + pageid + " " + ns;
}
}
}
The deserialiser for type pages simply checks the entries to find the one that contains a pageId and then populates the class.
Running my test gives you:
Queen (band) 42010 0
Queen (band) 12345 0
I am assuming that you are using Gson as your json library.
Regards,
Artur
Why do not use an JSON library like jackson or org.json?
Make your json correct like
{
"pages":{
"42010":{
"pageid":42010,
"ns":0,
"title":"Queen (band)"
}
}
}
And you will be able to use it like :
JSONObject jsonObjet = new JSONObject(yourJson);
jsonObjet.get("pages");
Ideally it should be using Map.
This helps in forming the values as Map<Integer, Pojo>.
Lets say
public class Pojo{
private int pageid;
private String title;
private int ns;
// getter and setter
}
This suffices the requirement of holding the random digits, generated at runtime,
The responses of a REST API always return a JSON with the following structure:
{
"status": "<status_code>",
"data": <data_object>
}
My problem is that the value of data doesn't have an unique type, but it can be a String, a JSON Object or a JSON Array, depending on the called endpoint. I can't figure out how to deserialize it in the right way to create the different Java objects...
For example, I've already prepared some POJOs: the root element
public class ApiResult {
#SerializedName("status")
public String status;
#SerializedName("data")
public JsonElement data; // should I define it as a JsonElement??
}
and two objects that reflects two of the endpoints:
// "data" can be a list of NavItems
public class NavItem {
#SerializedName("id")
public String id;
#SerializedName("name")
public String name;
#SerializedName("icon")
public String icon;
#SuppressWarnings("serial")
public static class List extends ArrayList<NavItem> {}
}
and
// "data" can be a single object representing a Profile
public class Profile {
#SerializedName("id")
public String id;
#SerializedName("fullname")
public String fullname;
#SerializedName("avatar")
public String avatar;
}
Reading some StackOverflow questions, I've seen I should use the JsonDeserializer<T> interface. But how if the type of data in ApiResult is variable?
You should use a a custom JsonDeserializer and write all your logic there, like this
ApiResult.java
public class ApiResult {
#SerializedName("status")
public String status;
#SerializedName("data")
public Object data;
}
ApiResultDeserializer.java
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
public class ApiResultDeserializer implements JsonDeserializer<ApiResult> {
private Type listType = new TypeToken<List<NavItem>>(){}.getType();
#Override
public ApiResult deserialize(JsonElement value, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject apiResultJson = value.getAsJsonObject();
final ApiResult result = new ApiResult();
result.status = apiResultJson.get("status").getAsString();
JsonElement dataJson = apiResultJson.get("data");
if(dataJson.isJsonObject()) {
result.data = context.deserialize(dataJson, NavItem.class);
} else if(dataJson.isJsonPrimitive()) {
result.data = context.deserialize(dataJson, String.class);
} else if(dataJson.isJsonArray()) {
result.data = context.deserialize(dataJson, listType);
}
return result;
}
}
and try to create different kinds of data (List, Object, or String) as you mentioned
Main.java
Gson gson = new GsonBuilder()
.registerTypeAdapter(ApiResult.class, new ApiResultDeserializer())
.create();
List<NavItem> navItems = new ArrayList<NavItem>();
for(int i = 1 ; i < 6 ; ++i) {
navItems.add(new NavItem(i+"", "Name-" + i, "Icon-" + i ));
}
ApiResult result = new ApiResult();
result.status = "OK";
result.data = navItems;
// Serialization
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}
result.data = navItems.get(0);
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}
result.data = "Test";
System.out.println(gson.toJson(result)); // {\"status\":\"OK\",\"data\":\"Test\"}
// Deserialization
String input = "{\"status\":\"OK\",\"data\":[{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"},{\"id\":\"2\",\"name\":\"Name-2\",\"icon\":\"Icon-2\"},{\"id\":\"3\",\"name\":\"Name-3\",\"icon\":\"Icon-3\"},{\"id\":\"4\",\"name\":\"Name-4\",\"icon\":\"Icon-4\"},{\"id\":\"5\",\"name\":\"Name-5\",\"icon\":\"Icon-5\"}]}";
ApiResult newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Array
input = "{\"status\":\"OK\",\"data\":{\"id\":\"1\",\"name\":\"Name-1\",\"icon\":\"Icon-1\"}}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // Object
input = "{\"status\":\"OK\",\"data\":\"Test\"}";
newResult = gson.fromJson(input, ApiResult.class);
System.out.println(newResult.data); // String
I managed to make it work as I wanted, and without using any custom deserializer!
For each endpoint, I wait for the response (btw I'm using Volley), then I first generate the "root" ApiResult object, check if the status is OK, then I proceed instantiating the data field as the requested type.
The POJOs are the same of the question. In ApiResult, "data" is a JsonElement.
// ... called the endpoint that returns a NavItem list
public void onResponse(String response) {
ApiResult rootResult = gson.fromJson(response.toString(), ApiResult.class);
if (rootResult.status.equals(STATUS_OK)) {
Log.d(LOG_TAG, response.toString());
NavItem.List resData = gson.fromJson(rootResult.data, NavItem.List.class); // <-- !!!!!
callback.onSuccess(resData);
}
else {
Log.e(LOG_TAG, response.toString());
callback.onError(-1, null);
}
}
Obviously the only thing to change for the "Profile" endpoint is the line with !!!!!
I am trying to parse using Jackson mapper to parse big JSON to java object. I have very big JSON but came across this little piece in it and not sure how to parse.
Here is the JSON, the format of it looks little different. I am trying to understand how can I parse it to an object.
{
"coordinates": [
[
[
-72.943068,
45.842298
],
[
-72.943075,
45.841859
]
]
]
}
I don't understand which format it is in, and how I can parse it to object.
It depends how really big is your JSON. If you can load it to memory, you can use the simplest way:
Solution 1:
POJO class:
class CoordinatesContainer {
private double[][][] coordinates;
public double[][][] getCoordinates() {
return coordinates;
}
public void setCoordinates(double[][][] coordinates) {
this.coordinates = coordinates;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder(1024);
for (double[] arrayItem : coordinates[0]) {
builder.append(Arrays.toString(arrayItem));
builder.append(", ");
}
return builder.toString();
}
}
Usage:
ObjectMapper mapper = new ObjectMapper();
CoordinatesContainer coordinatesContainer = mapper.readValue(json, CoordinatesContainer.class);
System.out.println(coordinatesContainer);
Above program prints:
[-72.943068, 45.842298], [-72.943075, 45.841859]
Solution 2:
But if your JSON is really big and you are not able to load it to memory, you should consider Jackson Streaming API. In this case you should not create POJO class and try to process each element "node" by "node":
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
public class JsonProgram {
public static void main(String[] args) throws IOException {
File json = new File("/x/data.json");
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(json);
// Skip all elements to first array
while (parser.nextToken() != JsonToken.START_ARRAY) {
}
parser.nextToken();
// First level
while (parser.nextToken() != JsonToken.END_ARRAY) {
// Skip inner start array element
parser.nextToken();
System.out.println();
System.out.println("NEXT ARRAY NODE");
BigDecimal first = parser.getDecimalValue();
// Go to second value
parser.nextToken();
BigDecimal second = parser.getDecimalValue();
// Skip inner end array element
parser.nextToken();
// Handle array item
System.out.println("First: " + first.toString());
System.out.println("Second: " + second.toString());
}
}
}
Above program prints:
NEXT ARRAY NODE
First: -72.943068
Second: 45.842298
NEXT ARRAY NODE
First: -72.943075
Second: 45.841859
In my examples I used Jackson in 2.2.3 version.
Create one json pojo mapper class
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"coordinates"
})
public class Example {
#JsonProperty("coordinates")
private List<List<List<Double>>> coordinates = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("coordinates")
public List<List<List<Double>>> getCoordinates() {
return coordinates;
}
#JsonProperty("coordinates")
public void setCoordinates(List<List<List<Double>>> coordinates) {
this.coordinates = coordinates;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
Then convert jsonString to pojo
Gson gson = new GsonBuilder().create();
Example r = gson.fromJson(jsonString, Example.class);
The server I am working with returns an json object which contains a list of objects, not just one.
{
"1":{"id":"1","value":"something"},
"2":{"id":"2","value":"some other thing"}
}
I want to convert this json object into an object array.
I know I can use Gson, and create a class like this:
public class Data {
int id;
String value;
}
and then use
Data data = new Gson().fromJson(response, Data.class);
But it's only for the objects inside the json object.
I don't know how to convert json object with number as keys.
Or alternatively I need to alter the server to response to something like this?:
{["id":"1","value":"something"],["id":"2","value":"some other thing"]}
But I don't want to change to server as I have to change all the client side codes.
Your JSON looks really weird. If you can't change it, you have to deserialize it to Map. Example source code could looks like this:
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public class GsonProgram {
public static void main(String... args) throws Exception {
Gson gson = new GsonBuilder().create();
String json = "{\"1\":{\"id\":\"1\",\"value\":\"something\"},\"2\":{\"id\":\"2\",\"value\":\"some other thing\"}}";
Type type = new TypeToken<HashMap<String, HashMap<String, String>>>() {}.getType();
Map<String, Map<String, String>> map = gson.fromJson(json, type);
for (Map<String, String> data : map.values()) {
System.out.println(Data.fromMap(data));
}
}
}
class Data {
private int id;
private String value;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
return "Data [id=" + id + ", value=" + value + "]";
}
public static Data fromMap(Map<String, String> properties) {
Data data = new Data();
data.setId(new Integer(properties.get("id")));
data.setValue(properties.get("value"));
return data;
}
}
Above program prints:
Data [id=2, value=some other thing]
Data [id=1, value=something]
Because this json object uses int as the field key that you cannot specify the field key name when deserialize it. Thus I need to extract the value set from the set first:
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(json).getAsJsonObject();
Set<Entry<String,JsonElement>> set = obj.entrySet();
Now "set" contains a set of , in my case is <1,{id:1,value:something}>.
Because the key is useless here, I only need the value set, so I iterate the set to extract the value set.
for (Entry<String,JsonElement> j : set) {
JsonObject value = (JsonObject) j.getValue();
System.out.println(value.get("id"));
System.out.println(value.get("value"));
}
If you have more complex structure, like nested json objects, you can have something like this:
for (Entry<String,JsonElement> j : locations) {
JsonObject location = (JsonObject) j.getValue();
JsonObject coordinate = (JsonObject) location.get("coordinates");
JsonObject address = (JsonObject) location.get("address");
System.out.println(location.get("location_id"));
System.out.println(location.get("store_name"));
System.out.println(coordinate.get("latitude"));
System.out.println(coordinate.get("longitude"));
System.out.println(address.get("street_number"));
System.out.println(address.get("street_name"));
System.out.println(address.get("suburb"));
}
Hope it helps.