I have to deserialise JSON. I use GSON library for this purpose. I am building a web application. The user fills up where he wants to fly, then the query is sent to the API and the result is returned.
Let us use an example of response:
{"success":true,"data":{"WAW":{"0":{"price":153,"airline":"LO","flight_number":678,"departure_at":"2019-08-05T17:40:00Z","return_at":"2019-08-20T14:35:00Z","expires_at":"2019-05-24T20:55:02Z"},"1":{"price":126,"airline":"A3","flight_number":881,"departure_at":"2019-11-21T11:00:00Z","return_at":"2019-11-26T16:05:00Z","expires_at":"2019-05-27T13:39:23Z"},"2":{"price":171,"airline":"KL","flight_number":900,"departure_at":"2019-09-12T02:40:00Z","return_at":"2019-09-18T17:30:00Z","expires_at":"2019-05-27T10:40:40Z"},"3":{"price":235,"airline":"B2","flight_number":972,"departure_at":"2019-06-12T07:20:00Z","return_at":"2019-06-18T17:30:00Z","expires_at":"2019-05-26T12:31:22Z"},"4":{"price":596,"airline":"TK","flight_number":422,"departure_at":"2019-06-20T00:10:00Z","return_at":"2019-06-24T13:05:00Z","expires_at":"2019-05-26T08:08:21Z"}}},"error":null,"currency":"EUR"}
I created a classes in this way: http://pojo.sodhanalibrary.com
The problem is that there is always the name of the place of arrival. In this case WAW = Warsaw.
gsonConvert.gson(output).getData().getWAW().getFirst().getAirline()
I want to avoid it because the place of arrival will depend on the choice of the user.
You should be able to simplify your data property to type: Map<String, Map<String, Flight>>. Where Flight POJO represents given flight. Model could look like below:
class FlightResponse {
private boolean success;
private Map<String, Map<String, Flight>> data;
private String error;
private String currency;
public Map<String, Flight> getFlatData() {
return data.entrySet()
.stream()
.collect(HashMap::new, (m, e) -> m.putAll(e.getValue()), Map::putAll);
}
// getters, setters, toString
}
class Flight {
private BigDecimal price;
private String airline;
#SerializedName("flight_number")
private int flightNumber;
#SerializedName("departure_at")
private String departureAt;
#SerializedName("return_at")
private String returnAt;
#SerializedName("expires_at")
private String expiresAt;
// getters, setters, toString
}
And example how to parse your JSON:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import java.io.File;
import java.io.FileReader;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder().create();
FlightResponse flightResponse = gson.fromJson(new FileReader(jsonFile), FlightResponse.class);
flightResponse.getFlatData().forEach((k, v) -> {
System.out.println(k + " => " + v);
});
}
}
Above code prints:
0 => Flight{price=153, airline='LO', flightNumber=678, departureAt='2019-08-05T17:40:00Z', returnAt='2019-08-20T14:35:00Z', expiresAt='2019-05-24T20:55:02Z'}
1 => Flight{price=126, airline='A3', flightNumber=881, departureAt='2019-11-21T11:00:00Z', returnAt='2019-11-26T16:05:00Z', expiresAt='2019-05-27T13:39:23Z'}
2 => Flight{price=171, airline='KL', flightNumber=900, departureAt='2019-09-12T02:40:00Z', returnAt='2019-09-18T17:30:00Z', expiresAt='2019-05-27T10:40:40Z'}
3 => Flight{price=235, airline='B2', flightNumber=972, departureAt='2019-06-12T07:20:00Z', returnAt='2019-06-18T17:30:00Z', expiresAt='2019-05-26T12:31:22Z'}
4 => Flight{price=596, airline='TK', flightNumber=422, departureAt='2019-06-20T00:10:00Z', returnAt='2019-06-24T13:05:00Z', expiresAt='2019-05-26T08:08:21Z'}
See also:
Flatten a Map> to Map with stream and lambda
Related
Not Looking for Jackson Solution.
List of Imports:
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.util.List;
import java.util.stream.Collectors;
JSON data:
{
"Name": "ABC.com",
"Developer": "Vishwa ratna",
"Project List": [
"Compnay: National",
"Compnay: Enterprise",
"Compnay: Alamo"
]
}
My program:
#SuppressWarnings("unchecked")
public static void main(String[] args)
{
File text = new File("R:/TestJSON.txt");
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(new FileReader("R:\\Desktop\\TestJSON.txt"));
JSONObject jsonObject = (JSONObject) obj;
List<String> list = new ArrayList<>();
String name = (String) jsonObject.get("Name");
System.out.println(name);
String author = (String) jsonObject.get("Developer");
System.out.println(author);
JSONArray companyList = (JSONArray) jsonObject.get("Project List");
companyList.stream().map(e->{if(e.equals("Compnay: National")); return e;}).collect(Collectors.toList());
list.forEach(System.out::println);
}
catch (Exception e)
{}
}
}
Everything is executing correctly except
companyList.stream().map(e->{if(e.equals("Compnay: National")); return e;}).collect(Collectors.toList());
list.forEach(System.out::println);
I can get the desired result another way around but i want to know why my map is not returning anything?? should it not return Compnay: National.
Result i am getting:
ABC.com
Vishwa ratna
I am expecting:
ABC.com
Vishwa ratna
Compnay: National
Edit: Even after using filter() as suggested by some people here i am unable to get the desired result.
One you have to use filter instead of map.
Second you have to assign the result of collect to list
Your code should be :
list = (List<String>) companyList.stream()
.filter(e-> e.equals("Compnay: National"))
.map(Object::toString)
.collect(Collectors.toList());
Another solution maybe is to after the filter collect toArray, thenyou convert the array to stream use map with Object::toString then collect to list, this can work also
list = Arrays.stream(
companyList.stream()
.filter(e -> e.equals("Compnay: National"))
.toArray())
.map(Object::toString)
.collect(Collectors.toList());
Why filter and not map
map alwayse return a result, so if you make a condition inside map you have to use else part, it works in your case because you have a typo, your condition not make any sinse, but why you use if inside a map, and you have filter where you can avoid conditions?
Stream<T> filter(Predicate<? super T> predicate)
Like you see filter took a predicate and return a new stream not a boolean like you said in your comment
Another solution using Jackson library
I'm not sure what library you are using to parse your Json file, but, it seems not the perfect way, I would suggest another solution using jackson library, so you have :
1- add to pom.xml the dependency :
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
2- create a Job which is similar to your Json file like so :
class MyObject {
private String name;
private String developer;
private List<String> projectList;
public String getName() {
return name;
}
#JsonSetter("Name")
public void setName(String name) {
this.name = name;
}
public String getDeveloper() {
return developer;
}
#JsonSetter("Developer")
public void setDeveloper(String developer) {
this.developer = developer;
}
public List<String> getProjectList() {
return projectList;
}
#JsonSetter("Project List")
public void setProjectList(List<String> projectList) {
this.projectList = projectList;
}
}
3- your code to parse
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public class Mcve {
public static void main(String[] args) throws IOException {
String path = "R:\\Desktop\\TestJSON.txt";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
MyObject obj = mapper.readValue(new File(path), MyObject.class);
String name = obj.getName();
System.out.println(name);
String author = obj.getDeveloper();
System.out.println(author);
List<String> list = obj.getProjectList()
.stream()
.filter(e -> e.equals("Compnay: National"))
.collect(Collectors.toList());
list.forEach(System.out::println);
}
}
Outputs
ABC.com
Vishwa ratna
Compnay: National
as you see, working with Objects is more easier and more helpful.
I want to retrieve information through a library that attacks Codewars API whit my user on JSON format.The request method(GET, gives you back a JSON with information about your user) https://www.codewars.com/api/v1/users/(here you put your user name) .First of all I added my name at the end of this URI to check that it works right and returns a JSON. After that I used a converter (JSON to Java) to get back the Java code corresponding of my JSON. http://www.jsonschema2pojo.org/
My JSON:
{
username: "heremyusername",
name: null,
honor: 2,
clan: "pepito",
leaderboardPosition: 500793,
skills: null,
ranks: {
overall: {
rank: -8,
name: "8 kyu",
color: "white",
score: 2
},
languages: {
java: {
rank: -8,
name: "8 kyu",
color: "white",
score: 2
}
}
},
codeChallenges: {
totalAuthored: 0,
totalCompleted: 1
}
}
My method:
public User getUser() {
RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForEntity("https://www.codewars.com/api/v1/users/(here my user name)", User.class).getBody();
return ;
}
Problem is that JSON to Java has given me back 6 differents Java class. One of them:
package entities;
import java.util.HashMap;
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({
"overall",
"languages"
})
public class Ranks {
#JsonProperty("overall")
private Overall overall;
#JsonProperty("languages")
private Languages languages;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String,
Object>();
#JsonProperty("overall")
public Overall getOverall() {
return overall;
}
#JsonProperty("overall")
public void setOverall(Overall overall) {
this.overall = overall;
}
#JsonProperty("languages")
public Languages getLanguages() {
return languages;
}
#JsonProperty("languages")
public void setLanguages(Languages languages) {
this.languages = languages;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
It give me back another 5 Java class(Java,Overall,Mail,Languages,CodeChallenge) with same format(getters,setters...) So method public User getUser() its wrong, I donĀ“t know what it returns,if I have to make it void or what class to use because I want to retrieve all information not only a portion.
User is a JavaBean whose fields are Integer id and String username.
I'm having tough time coming up with a class that represents the following JSON. "familyRelationships" is an array of arrays. Each array has person1 and person2 identifiers and the relationship between person1 and person2. As you might've already guessed, order of the values in array is very important. For example, if "12345" and "31142" positions switch, then it means "31142" is the PARENT of "12345" which is totally wrong.
{
"familyRelationships": [
[
"12345",
"SPOUSE",
"67890",
{
"careTaker": false,
"liveTogether": true
}
],
[
"12345",
"PARENT",
"31142",
{
"careTaker": true,
"liveTogether": true
}
],
[
"67890",
"PARENT",
"31142",
{
"careTaker": true,
"liveTogether": true
}
]
]
}
This should be doable using a custom deserializer.
A relationship in my opinion should be modeled as a proper java class with proper names. Note that the constructor takes a JSONNode as an argument and that I have left our any getters and setters:
public class Relationship {
private final String id1;
private final String id2;
private final Relation relation;
private final boolean careTaker;
private final boolean liveTogether;
public Relationship(JsonNode base) {
this.id1 = base.get(0).asText();
this.id2 = base.get(2).asText();
this.relation = Relation.valueOf(base.get(1).asText());
this.careTaker = base.get(3).get("careTaker").asBoolean();
this.liveTogether = base.get(3).get("liveTogether").asBoolean();
}
public enum Relation {
PARENT,
SPOUSE;
}
}
We also need a class which stores the collection. This is the one that you would deserialize the top level object into (again leaving out getters and setters):
#JsonDeserialize( using = FamillyRelationshipsDeserializer.class )
public class FamillyRelationships {
public List<Relationship> familyRelationships = new ArrayList<>();
}
Finally we need to implement the actual JsonDeserializer referenced in the above class. It should look something like the following. I used this question as a reference:
class FamillyRelationshipsDeserializer extends JsonDeserializer<FamillyRelationships> {
#Override
public FamillyRelationships deserialize(JsonParser jp, DeserializationContext ctxt) throws
IOException, JsonProcessingException {
FamillyRelationships relationships = new FamillyRelationships();
JsonNode node = jp.readValueAsTree();
JsonNode rels = node.get("familyRelationships");
for (int i = 0; i < rels.size(); i++) {
relationships.familyRelationships.add(new Relationship(rels.get(i));
}
return relationships;
}
}
I hope this helps, I haven't actually tested any of this, it probably will not even compile, but the principles should be right. I have also assumed that the JSON is of the format you supplied. If that's not a guarantee then you will need to make the proper checks and deal with any deviations.
If you also want to be able to serialize everything then you will need to implement JsonSerializer see this question for more details.
That JSON isn't going to nicely become a POJO because the types contained within the arrays are not consistent:
e.g.
[
"12345",
"SPOUSE",
"67890",
{
"careTaker": false,
"liveTogether": true
}
]
is String,String,String,Object. The only way that works is if you have a Object[] or List<Object>. So familyRelationships should actually be a List<List<Object>>. The end result is going to require a bunch of casting (and probably some checks to make sure that the item at a given index is the class you expect), but it will work.
There are some tools online to do that for you
package com.example;
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({
"familyRelationships"
})
public class Example {
#JsonProperty("familyRelationships")
private List<List<String>> familyRelationships = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("familyRelationships")
public List<List<String>> getFamilyRelationships() {
return familyRelationships;
}
#JsonProperty("familyRelationships")
public void setFamilyRelationships(List<List<String>> familyRelationships) {
this.familyRelationships = familyRelationships;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
This is only 1 possiblilty made with http://www.jsonschema2pojo.org/
Currently, I plan to convert the following Map<String, String> to List of POJO.
Map<String, String> m = new HashMap<>();
m.put("stock_price_alerts", "[{\"code\":\"KO\",\"rise_above\":7.0,\"size\":1000.89,\"price\":10,\"time\":1519625135173,\"fall_below\":null},{\"code\":\"BRK-B\",\"rise_above\":180,\"size\":100,\"price\":190,\"time\":1519160399911,\"fall_below\":null}]");
System.out.println(m);
When we print out the Map at console, it looks like
{stock_price_alerts=[{"code":"KO","rise_above":7.0,"size":1000.89,"price":10,"time":1519625135173,"fall_below":null},{"code":"BRK-B","rise_above":180,"size":100,"price":190,"time":1519160399911,"fall_below":null}]}
I prepare 2 POJO classes.
StockPriceAlert.java
package sandbox;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class StockPriceAlert {
#SerializedName("code")
#Expose
public String code;
#SerializedName("fall_below")
#Expose
public Object fallBelow;
#SerializedName("rise_above")
#Expose
public long riseAbove;
#SerializedName("price")
#Expose
public long price;
#SerializedName("time")
#Expose
public long time;
#SerializedName("size")
#Expose
public long size;
/**
* No args constructor for use in serialization
*
*/
public StockPriceAlert() {
}
/**
*
* #param fallBelow
* #param time
* #param price
* #param riseAbove
* #param code
* #param size
*/
public StockPriceAlert(String code, Object fallBelow, long riseAbove, long price, long time, long size) {
super();
this.code = code;
this.fallBelow = fallBelow;
this.riseAbove = riseAbove;
this.price = price;
this.time = time;
this.size = size;
}
}
StockPriceAlertResponse.java
package sandbox;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class StockPriceAlertResponse {
#SerializedName("stock_price_alerts")
#Expose
private List<StockPriceAlert> stockPriceAlerts = null;
/**
* No args constructor for use in serialization
*
*/
public StockPriceAlertResponse() {
}
/**
*
* #param stockPriceAlerts
*/
public StockPriceAlertResponse(List<StockPriceAlert> stockPriceAlerts) {
super();
this.stockPriceAlerts = stockPriceAlerts;
}
public List<StockPriceAlert> getStockPriceAlerts() {
return stockPriceAlerts;
}
public void setStockPriceAlerts(List<StockPriceAlert> stockPriceAlerts) {
this.stockPriceAlerts = stockPriceAlerts;
}
}
I wrote the following code to perform conversion.
package sandbox;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* #author yccheok
*/
public class Main {
public static void main(String[] args) {
Map<String, String> m = new HashMap<>();
m.put("stock_price_alerts", "[{\"code\":\"KO\",\"rise_above\":7.0,\"size\":1000.89,\"price\":10,\"time\":1519625135173,\"fall_below\":null},{\"code\":\"BRK-B\",\"rise_above\":180,\"size\":100,\"price\":190,\"time\":1519160399911,\"fall_below\":null}]");
System.out.println(m);
final Gson gson = new GsonBuilder().create();
JsonElement jsonElement = gson.toJsonTree(m);
StockPriceAlertResponse stockPriceAlertResponse = gson.fromJson(jsonElement, StockPriceAlertResponse.class);
List<StockPriceAlert> stockPriceAlerts = stockPriceAlertResponse.getStockPriceAlerts();
}
}
However, I get the following Exception
Exception in thread "main" com.google.gson.JsonSyntaxException:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING
Any idea how I can resolve this?
Note, Map<String, String> m is output from 3rd party library. So, that input is out of my control.
Do simply:
StockPriceAlertResponse res = gson.fromJson(""+m, StockPriceAlertResponse.class);
When m.toString() is called it generates something starting like:
{ stock_price_alerts= ...
but Gson seems to handle = even it should be :.
Note: you need to change
public long size;
to:
public float size;
because of values like:
"size": 1000.89
The problem is that the original map's values will stay a String.
String mJsonString = m.entrySet().stream()
.map(e -> String.format("\"%s\":%s", e.getKey(), e.getValue()))
.collect(Collectors.joining(",", "{", "}"));
System.out.println(mJsonString);
final Gson gson = new GsonBuilder().create();
StockPriceAlertResponse stockPriceAlertResponse = gson.fromJson(mJsonString,
StockPriceAlertResponse.class);
List<StockPriceAlert> stockPriceAlerts = stockPriceAlertResponse.getStockPriceAlerts();
In the data is an error, for size: a floating point value that should have been integer.
Your problem is that you convert something that already is JSON into JSON again by calling toJsonTree. When you then call fromJson on the result it will convert it back to what you originally gave it: A Map<String, String>.
You have to call fromJson directly on the values in your map. Since the value you have is an array for whatever reason (enclosed by [ and ]), you have to first use fromJson with JsonArray.class, and then fromJson on the first element in the resulting array with your StockPriceAlert.class:
for (String value : m.values())
{
StockPriceAlert stockPriceAlert = gson.fromJson(gson.fromJson(value, JsonArray.class).get(0), StockPriceAlert.class);
}
I'm trying to parse a string with Gson library but without success. Here is my string:
[["-1.816513","52.5487566"],["-1.8164913","52.548824"]]
the problem in this example is that there are no key-value pairs. I looked at other examples but all of them had key-value pairs and didn't look like my problem.
My solution to parse a list of list of strings.
package stackoverflow.answers;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class GsonTest {
public static void main(String[] arg) {
Gson gson = new Gson();
String jsonOutput = "[[\"-1.816513\",\"52.5487566\"],[\"-1.8164913\",\"52.548824\"]]";
Type listType = new TypeToken<List<List<String>>>() {}.getType();
List<List<String>> strings = (List<List<String>>) gson.fromJson(jsonOutput, listType);
for(List<String> inner: strings){
for(String s: inner){
System.out.println(s);
}
}
}
}
But since values can be "thinked" also a doubles, you can parse them directly changing type into solution:
package stackoverflow.answers;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class GsonTest {
public static void main(String[] arg) {
Gson gson = new Gson();
String jsonOutput = "[[\"-1.816513\",\"52.5487566\"],[\"-1.8164913\",\"52.548824\"]]";
Type listType = new TypeToken<List<List<Double>>>() {}.getType();
List<List<Double>> numbers = (List<List<Double>>) gson.fromJson(jsonOutput, listType);
for(List<Double> inner: numbers){
for(Double d: inner){
System.out.println(d);
}
}
}
}
Not important in the context, but for future references: Java 7, Gson 2.2.4
One solution, with raw types:
package com.stackoverflow.so18525283;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;
public class App {
private static final String INPUT = "[[\"-1.816513\",\"52.5487566\"],[\"-1.8164913\",\"52.548824\"]]";
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().create();
final List<?> fromJson = gson.fromJson(INPUT, List.class);
if (fromJson != null && fromJson.size() > 0 && fromJson.get(0) instanceof List) {
System.out.println(((List<?>) fromJson.get(0)).get(0)); // this is a String
}
}
}
Another solution is to recreate a valid JSON object, same App as below but with:
public static void main(String[] args) {
final Gson gson = new GsonBuilder().create();
final Foo fromJson = gson.fromJson("{ data: " + INPUT + "}", Foo.class);
// TODO: check for null
System.out.println(fromJson.data.get(0).get(0)); // this is a Double
}
private static class Foo {
List<List<Double>> data;
}