I'm trying to convert following string to an object,
{
"base":"TRY",
"date":"2017-01-06",
"rates":{
"EUR":0.37679,
"USD":0.51059,
"BRL":0.88513,
"CAD":0.36651,
...
}
}
I know, if I create an object containing all rates as Double attribute, I will be able to convert that object into an object. But I want rates to be in an array like below. How can I create convertedJsonObjectArrayList. Thanks in advance.
List<Rate> rates = convertedJsonObjectArrayList;
class Rate{
String name; //EUR
Double value; //0.37679
}
This assumes that you're guaranteed to have content within the JSON object associated to the name "rates" that will be parseable as a List.
Define a custom deserializer to consume the name-value tokens as pairs
class RatesJsonObjectToArray extends JsonDeserializer<List<Rate>> {
#Override
public List<Rate> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
List<Rate> rates = new ArrayList<>();
// consume two tokens at a time, a name and a value
for (;;) {
String name = p.nextFieldName();
if (name == null) {
break; // no more input
}
JsonToken doubleValueToken = p.nextValue();
if (doubleValueToken != JsonToken.VALUE_NUMBER_FLOAT) { // there's also VALUE_NUMBER_INT for more flexibility
throw new JsonParseException("Expected a numeric value.");
}
double value = p.getDoubleValue();
rates.add(new Rate(name, value));
}
return rates;
}
}
Then annotate your rates field/setter with this JsonDeserializer
#JsonProperty
#JsonDeserialize(using = RatesJsonObjectToArray.class)
private List<Rate> rates;
You can get fancy and write a custom deserializer.
However, if you don't mind being a bit more quick-and-dirty, you can just deserialize to a Map, and convert to your preferred structure explicitly:
String ratesAsJson = "{ \"EUR\" : 0.2, \"USD\":1.0 }";
ObjectMapper objectMapper = new ObjectMapper();
TypeReference<Map<String, Double>> typeRef = new TypeReference<Map<String, Double>>() {};
Map<String, Double> map = objectMapper.readValue(ratesAsJson, typeRef);
List<Rate> list = map.entrySet().stream()
.map(entry -> new Rate(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
(I've assumed a 2-arg constructor on the Rate class).
If you write a custom deserializer, it will have very similar code to the above within it.
I've used a stream and map(). You could use a for loop, but hey, it's 2017!
If you want to convert the currency object(json) into an array(java) You can not use serialization(gson/jaxb) you would need to parse (JsonParser) json and make the conversion yourself
Here is my solution;
ObjectMapper mapper = new ObjectMapper();
CurrencyServiceResponse currencyResp =
mapper.readValue(jsonStr,new TypeReference<CurrencyServiceResponse>(){});
currencyResp.setRates(new ArrayList<>());
for(Map.Entry<String,Double> entry: currencyResp.getRateMap().entrySet()){
currencyResp.getRates().add(
new CurrencyServiceResponse.Rate(entry.getKey(),entry.getValue()));
}
And class for conversion
public class CurrencyServiceResponse {
#JsonProperty("base")
private String base;
#JsonProperty("date")
private Date date;
#JsonProperty("rates")
private Map<String,Double> rateMap;
#JsonIgnore
private List<Rate> rates;
//getters&setters
public static class Rate{
private String name;
private Double value;
public Rate(String name, Double value) {
this.name = name;
this.value = value;
}
//getters&setters
}
}
You can use GSON library. It's the Google JSON library.
JsonParser jp = new JsonParser();
JsonElement root = jp.parse(YOUR_DATA);
JsonObject rootobj = root.getAsJsonObject();
And in your case to access to the EUR rate you can use :
Double rate_eur = rootobj.get("rates").getAsJsonObject().get("EUR").getAsDouble();
Don't forget to import classes :
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
And dependencies :
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>RELEASE</version>
</dependency>
for maven for example.
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
Is it possible to convert map into a pojo when attribute names are different?
I am extracting raw input into a map to have the following data. Data can vary based on message type. For example:
for Message type = STANDARD
Map<String, Double> data = new HashMap<>();
data.set('TEMP', 18.33);
data.set('BTNUM', 123);
for Message type = NON_STANDARD
Map<String, Double> data = new HashMap<>();
data.set('GPSLAT', 12.33);
data.set('GPSLON', 42.33);
For each message type I have a Java model class
#Data
public class StandardMessage {
private String longitude;
private String latitude;
}
#Data
public class NonStandardMessage {
private String temperature;
private String btNumber;
}
Currenly I am mapping data to POJO class manually like below
StandardMessage sm = new StandardMessage();
sm.setLongitude(data.get('GPSLON'));
NonStandardMessage nsm = new NonStandardMessage();
nsm.setTemperature(data.get('TEMP'));
Is it possible to make above mapping generic? i.e setting object property without knowing name?
In Typescript we can achieve this easily by defining configuration like:
objectPropertyMapping = new Map();
objectPropertyMapping.set('GPSLAT', 'latitude');
objectPropertyMapping.set('GPSLON', 'longitude');
standardMessage = {};
data.forEach((value: boolean, key: string) => {
standardMessage[ObjectPropertyMapping.get(key)] = data[key];
});
https://stackblitz.com/edit/angular-zjn1kc?file=src%2Fapp%2Fapp.component.ts
I know Java is a statically-typed language, just wondering is there a way to achieve this like typescript or we have to map manually all the time?
We use jackson-databind. It uses annotations for configuration.
Here are some example:
The entity class:
class MessageRequest {
#JsonProperty("A")
private String title;
#JsonProperty("B")
private String body;
... getters and setters ...
}
The main method:
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> source = new HashMap<>();
source.put("A", "This is the title");
source.put("B", "Here is the body");
MessageRequest req = objectMapper.convertValue(source, MessageRequest.class);
System.out.println(req.getTitle());
System.out.println(req.getBody());
}
I'm trying to use the Java Jackson ObjectMapper to parse a three level JSON object stucture with dynamic keys. I tried the following:
public class AssetsPushManifest {
private Map<String, List<Asset>> manifest = new HashMap<>();
public void addPushManifest(Resource manifestResource) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, Map<String, Asset>> manifestData = mapper.readValue(manifestResource.getInputStream(), new TypeReference<Map<String, Map<String, Asset>>>() {});
for (String requestedPathName : manifestData.keySet()) {
if (!this.manifest.containsKey(requestedPathName)) {
this.manifest.put(requestedPathName, new LinkedList());
}
List<Asset> requestedPath = this.manifest.get(requestedPathName);
for (String servePath : manifestData.get(requestedPathName).keySet()) {
Asset asset = manifestData.get(requestedPathName).get(servePath);
asset.path = servePath;
requestedPath.add(asset);
}
}
...
}
public class Asset {
public String path;
public String type;
public Integer weight;
}
}
To parse this:
{
"theme/test-theme/index.html": {
"theme/test-theme/somestyling.css": {
"type": "document",
"weight": 1
}
}
}
But it won't work, why oh why? Is it too many levels? (still Java beginner here)
The end goal is to parse the several JSON structures like above into a structure like Map> so any other ways of doing this would also be fine.
I would solve this in a different way: parse the json into a map: if you give Jackson a map as type reference, it will deserialize the JSON into multi-level map:
`Map<String, Object> manifestData = mapper.readValue(manifestResource.getInputStream(), Map.class);`
Now that the json parsing hurdle is behind us, it is easier to construct an instance of an Asset by querying the map. I would do it by adding a constructor to the Asset class:
public Asset(Map<String, Object> manifestData) {
Map<String, Object> assetData = (Map<String, Object>)manifestData.values().iterator().next(); // get the single value of the map
this.path = assetData.keySet().iterator().next();
this.type = assetData.get("type");
this.weight = assetData.get("weight");
}
I'm receiving a Supplier into a method that will create an object based on each change that was made according to a JsonDiff. The Supplier is set up like so:
ObjectMapper mapper = new ObjectMapper();
// jsons to compare
String json1= "{}";
String json2= "{\"name\":\"Sean\", \"state\":\"Colorado\"}";
// convert jsons to JsonNode
JsonNode json1Node = mapper.readTree(json1);
JsonNode json2Node = mapper.readTree(json2);
// create Supplier<InputStream>
Supplier<InputStream> diffs = () ->
new ByteArrayInputStream(JsonDiff.asJson(json1Node, json2Node).toString().getBytes());
The resulting diff (between converting it into an InputStream) would look something like this:
[{"op":"add", "path": "/name", "value": "Sean"}, {"op":"add", "path": "/State", "value": "Colorado"}]
When I receive this supplier, I want to be able to separate each patch into an object. The object would simply store information, set up like:
private enum Op {
ADD, REPLACE, REMOVE
}
private Op op;
private String path;
private String value;
public Patch(Op op, String path, String value {
this.op = op;
this.path = path;
this.value = value;
}
// getter methods
From this particular example, I should be able to create 2 objects, 1 for the name, and 1 for the state.
My attempt was like so:
private List<Patch> patches;
Map<String, String> map = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
try {
// convert JSON string to Map
map = mapper.readValue(diffs.get().toString(), new TypeReference<HashMap<String, String>>() {});
// create patches
for (String value : map.values()) {
HashMap<String, String> result = mapper.readValue(value, HashMap.class);
patches.add(new Patch(Patch.Op.valueOf(result.get("op").toUpperCase()), result.get("path"), result.get("value")));
}
}catch (IOException e) {
e.printStackTrace();
}
It throws the following exception on the "readValue" line:
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'java': was expecting ('true', 'false' or 'null')
at [Source: java.io.ByteArrayInputStream#1abf3bf; line: 1, column: 5]
It seems (at least this is my interpretation) that the diff.get().toString() call isn't returning anything remotely close to Json.
Does anyone know how to do this?
Since the result of the jsondiff is an array, you might have an issue getting it parsed into a map instead of a list.
Further more you shouldn't have to call the toString() method of the Supplier output.
Here is a working example using a recent version of jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.1-1</version>
</dependency>
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-patch</artifactId>
<version>1.9</version>
</dependency>
public class Json {
static class Patch {
enum Op {
ADD, REMOVE, REPLACE;
};
private final Op op;
private final String path;
private final String value;
#JsonCreator
public Patch(//
#JsonProperty("op") String op, //
#JsonProperty("path") String path, //
#JsonProperty("value") String value) {
this.op = Op.valueOf(op.toUpperCase());
this.path = path;
this.value = value;
}
#Override
public String toString() {
return "Patch [op=" + op + ", path=" + path + ", value=" + value + "]";
}
}
public static void main(String[] args) throws Exception {
// jsons to compare
String json1 = "{}";
String json2 = "{\"name\":\"Sean\", \"state\":\"Colorado\"}";
// convert jsons to JsonNode
JsonNode json1Node = mapper.readTree(json1);
JsonNode json2Node = mapper.readTree(json2);
// create Supplier<InputStream>
final String strDiff = JsonDiff.asJson(json1Node, json2Node).toString();
Supplier<InputStream> diffs = () -> new ByteArrayInputStream(strDiff.getBytes());
MappingIterator<Patch> iterator = new ObjectMapper()//
.readerFor(Patch.class) //
.readValues(diffs.get());
List<Patch> patches = new ArrayList<>();
while (iterator.hasNext()) {
patches.add(iterator.next());
}
System.out.println(patches);
}
}
output is:
[Patch [op=ADD, path=/name, value=Sean], Patch [op=ADD, path=/state, value=Colorado]]
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.