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.
Related
I have JSON String, received from HTTP request:
[
{
"id":15,
"title":"1",
"description":"desc",
"user_id":152
},
{
"id":18,
"title":"2",
"description":"desc",
"user_id":152
},
{
"id":19,
"title":"tab3",
"description":"zadanka",
"user_id":152
}
]
How to convert it into an ArrayList of Objects?
Using Gson
Gson gson = new Gson();
ArrayList<Object> listFromGson = gson.fromJson("json string",
new TypeToken<ArrayList<Object>>() {}.getType());
Using Jackson
ObjectMapper mapper = new ObjectMapper();
ArrayList<Object> listFromJackson = mapper.readValue("json string",
new TypeReference<ArrayList<Object>>(){});
If you could define a pojo as
public class Example {
private Integer id;
private String title;
private String description;
private Integer userId;
// setters / getters
}
Then
ArrayList<Example> listFromGson = gson.fromJson("json string",
new TypeToken<ArrayList<Example>>() {}.getType());
ArrayList<Example> listFromJackson = mapper.readValue("json string",
new TypeReference<ArrayList<Example>>(){});
Also, you should prefer using List instead of ArrayList.
You need to declare a pojo
class Data{
String id;
String title;
String description;
String userId;
//Generate setter an getter
}
The iterate over json like following:
JSONArray jsonArr = new JSONArray("[your JSON Stirng]");
List<Data> dataList = new ArrayList<Data>();
for (int i = 0; i < jsonArr.length(); i++) {
JSONObject jsonObj = jsonArr.getJSONObject(i);
Data data = new Data();
data.setId(jsonObj.getString("id"));
data.setTitle(jsonObj.getString("title"));
data.setDescription(jsonObj.getString("description"));
data.setUserId(jsonObj.getString("user_id"));
dataList.add(data);
}
You also need json jar. You can download from here
If you are using RestApi then use the annotation #RequestBody with your pojo class.
#RequestMapping(value="/your api name", method=RequestMethod.POST)
public ResponseData createUser(#RequestBody MyPojo myPojo){
System.out.println("Creating User "+myPojo.toString());
//Here you will able to access your request data from myPojo object
}
Make your pojo class:
public class MyPojo
{
private Data[] data;
public Data[] getData ()
{
return data;
}
public void setData (Data[] data)
{
this.data = data;
}
#Override
public String toString()
{
return "ClassPojo [data = "+data+"]";
}
}
public class Data
{
private String id;
private String title;
private String description;
private String user_id;
public String getId ()
{
return id;
}
public void setId (String id)
{
this.id = id;
}
public String getTitle ()
{
return title;
}
public void setTitle (String title)
{
this.title = title;
}
public String getDescription ()
{
return description;
}
public void setDescription (String description)
{
this.description = description;
}
public String getUser_id ()
{
return user_id;
}
public void setUser_id (String user_id)
{
this.user_id = user_id;
}
#Override
public String toString()
{
return "ClassPojo [id = "+id+", title = "+title+", description = "+description+", user_id = "+user_id+"]";
}
}
In addition to #Sudhir, I would recommend to use Gson
Gson gson = new GsonBuilder().create();
Data p = gson.fromJson(jsonString, Data.class);
// Or to array.
Data[] data = gson.fromJson(jsonString, Data[].class);
Here is my API
http://services.groupkt.com/country/get/all
I'm confused on how to use the Json reader methods. I tried looking on its Javadoc, it seems the straightforward but when I implement it, it has different behavior.
Here is my code
RestResponse result = null;
String countryName = null;
String alpha2Code = null;
String alpha3Code = null;
jsonReader.beginObject();
jsonReader.beginArray();
while (jsonReader.hasNext()) {
countryName = jsonReader.nextString();
alpha2Code = jsonReader.nextString();
alpha3Code = jsonReader.nextString();
}
jsonReader.endArray();
jsonReader.beginArray();
while (jsonReader.hasNext()) {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
jsonReader.skipValue();
jsonReader.peek();
}
jsonReader.endObject();
}
jsonReader.endArray();
This is my code for learning how does it navigates my json. This code runs on the read method of TypeAdapter.
Can you provide me samples on how can I easily understand how to use json reader methods correctly?
If you want to learn basic jSON parsing you should definitely read this Android Json Parsing ..... but in retrofit 2 you can use Model classes rather than json parsing.....I'm sharing my code below....
Model Class
public class WeatherResponse {
#SerializedName("cod")
#Expose
private String cod;
#SerializedName("message")
#Expose
private Double message;
#SerializedName("cnt")
#Expose
private Double cnt;
#SerializedName("list")
#Expose
private List<cityList> list = null;
#SerializedName("city")
#Expose
private City city;
public String getCod() {
return cod;
}
public void setCod(String cod) {
this.cod = cod;
}
public Double getMessage() {
return message;
}
public void setMessage(Double message) {
this.message = message;
}
public Double getCnt() {
return cnt;
}
public void setCnt(Double cnt) {
this.cnt = cnt;
API Client
public class ApiClient {
private static final int TIME_OUT = 30;
public static final String BASE_URL = "http://api.openweathermap.org/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
okBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
okBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
okBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
Gson gson = new GsonBuilder().create();
return new Retrofit.Builder()
.baseUrl(BASE_URL) .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okBuilder.build())
.build();
}
return retrofit;
}
}
API Interface
public interface ApiInterface {
#GET("data/2.5/forecast?id=524901")
Call<WeatherResponse> getWeatherData(#Query("APPID") String apiKey);
}
Be easy, just try GSON. There are many examples, articles about that
https://guides.codepath.com/android/Consuming-APIs-with-Retrofit#overview
https://medium.freecodecamp.com/rxandroid-and-retrofit-2-0-66dc52725fff#.ymmfqdi9s
https://zeroturnaround.com/rebellabs/getting-started-with-retrofit-2/
According http://services.groupkt.com/country/get/all response here is GSON models
public class County {
#SerializedName("name") public String name;
#SerializedName("alpha2_code") public String alpha2Code;
#SerializedName("alpha3_code") public String alpha3Code;
}
public class RestResponse {
#SerializedName("messages") public Messages messages;
#SerializedName("result") public Countries counties;
}
public class CountriesResponse {
#SerializedName("RestResponse") public RestResponse restResponse;
}
public interface GroupktApi {
#GET("/country/get/all")
Call<CountriesResponse> getAllCountries()
}
public Gson provideGson() {
return new GsonBuilder().registerTypeAdapter(Messages.class, MessagesDeserializer());
}
public class MessagesDeserializer extend JsonDeserializer<Messages> {
#Override public Messages deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
List<String> messages = new ArrayList();
if (json.isJsonArray()) {
Type listType = new TypeToken<ArrayList<String>>(){}.getType();
List<String> arrayMessages = context.deserialize<List<String>>(value, listType)
messages.addAll(arrayMessages)
} else {
String message = json.asString()
messages.add(message)
}
return new Messages(messages);
}
}
public class Messages {
public List<String> messages;
public Messages (List<String> messages) {
this.messages = messages;
}
}
Countries in the same way
That's it
I have a web-service in Java with Jersey to create and edit prices of a market. To do that, I send a JSON object containing the market informations, including another JSON object for the prices.
For example, this is the JSON I'm posting through Postman:
{
"name": "Market 01",
"address": "Market 01 street",
"prices": "{\"watermelon\": \"5.40\", \"melon\": \"2.55\"}"
}
On the web-server side, I try to create a list of the prices using GSON, but I can't get it to work. My objective here is to check on the difference between the new prices and the current prices. Below, there is my POJO Price.java, what I'm trying to do on the Controller for the edit and the Exception I'm geting on Postman:
POJO - Price.java
public class Price {
private String nome;
private Double preco;
//Getters and setters also
}
MarketController.java
Collection<Price> prices = gson.fromJson(json, new TypeToken<List<Price>>(){}.getType());
Exception raised on MarketController.java:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
EDIT: The solution that worked for me based on #A2H response:
POJO class - Price.java
public class Price {
private String name;
private Double price;
...
#Override
public String toString() {
return "{\"name\":\"" + name + "\", \"price\":" + price + "}";
}
}
POJO class - Market.java
public class Market{
...
//Include as a List<Price>
private List<Price> prices;
...
}
MarketController.java
// When going from List<Price> to JSON String
String prices = gson.toJson(market.getPrices());
// When going from JSON String to List<Price>
List<Price> prices = gson.fromJson(jsonString, new TypeToken<List<Price>>(){}.getType());
This code is well rounded for this situation, where you need to transform from List to JSON String and vice-versa.
Your POJO implies that you should have an array of prices in your JSON object.
Here's a full working example.
package test;
import java.util.List;
import com.google.gson.Gson;
public class TESTTEST {
public class MarketInfo {
String name;
String address;
List<Price> prices;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Price> getPrices() {
return prices;
}
public void setPrices(List<Price> prices) {
this.prices = prices;
}
}
public class Price {
String nome;
Double preco;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public Double getPreco() {
return preco;
}
public void setPreco(Double preco) {
this.preco = preco;
}
#Override
public String toString() {
return "{\"nome\":\"" + nome + "\", \"preco\":" + preco + "}";
}
}
public static void main(String[] args) {
Gson gson = new Gson();
String jsonString = "{\"name\": \"Market 01\",\"address\": \"Market 01 street\","
+ "\"prices\": [{\"nome\":\"watermelon\",\"preco\":\"5.40\"}, {\"nome\":\"melon\",\"preco\": \"2.55\"}]}";
MarketInfo res = gson.fromJson(jsonString, MarketInfo.class);
System.out.println(res.getPrices());
}
}
That's because a List would be represented by a JSON array, not by an object as you provide. You can try to deserialize to a map (or, send an array).
This is a follow up question to this question:
Passing custom type query parameter
I got a class which includes this method:
public static MyClass fromString(String json)
{
Gson gson = new Gson();
MyClass user = gson.fromJson(json, MyClass.class);
return user;
}
The full class:
public class MyClass
{
public String name;
public PortalNameEnum portalName;
public PortalUserTypeEnum portalUserType;
public String notes;
public MyClass(String name, PortalNameEnum portalName,
PortalUserTypeEnum portalUserType, String notes)
{
super();
this.portalName = portalName;
this.portalUserType = portalUserType;
this.name = name;
this.notes = notes;
}
public static MyClass fromString(String json)
{
Gson gson = new Gson();
PortalUserInfo user = gson.fromJson(json, PortalUserInfo.class);
return user;
}
public PortalNameEnum getPortalName()
{
return portalName;
}
public void setPortalName(PortalNameEnum portalName)
{
this.portalName = portalName;
}
public PortalUserTypeEnum getPortalUserType()
{
return portalUserType;
}
public void setPortalUserType(PortalUserTypeEnum portalUserType)
{
this.portalUserType = portalUserType;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getNotes()
{
return notes;
}
public void setNotes(String notes)
{
this.notes = notes;
}
}
I got a resource which got a method:
#Path("/myclasscall")
#GET
#UnitOfWork
public String registerPortalUser(#Context HttpServletRequest req, #QueryParam("callback") String callback, #QueryParam("myclass") MyClass recordData) throws Throwable
{ .. }
It seems like the fromString method is not called and the resource method is always null, even though I see in the console the request itself and I do see a string that has been passed. Why is that?
The problem was with the client.
Instead of passing a single parameter called "myclass", he passed all the fields separately. After merging them together into a single Json instance, it was fixed.
I'm being given a Json file with the form:
{
"descriptions": {
"desc1": "someString",
"desc2": {"name":"someName", "val": 7.0}
}
}
I have the POJO:
public class CustomClass {
Map<String, Object> descriptions;
public static class NameVal{
String name;
double val;
public NameVal(String name, double val){...}
}
}
I can recreate the json file with the code:
CustomClass a = new CustomClass();
a.descriptions = new HashMap<String, Object>();
a.descriptions.put("desc1", "someString");
a.descriptions.put("desc2", new CustomClass.NameVal("someName", 7.0));
new ObjectMapper().writeValue(new File("testfile"), a);
But, when I read the object back in using:
CustomClass fromFile = new ObjectMapper().readValue(new File("testfile"), CustomClass.class);
then fromFile.descriptions.get("desc2") is of type LinkedHashMap instead of type CustomClass.NameVal.
How can I get Jackson to properly parse the type of the CustomClass.NameVal descriptors (other than making some class that wraps the parsing and explicitly converts the LinkedHashMap after Jackson reads the file)?
Try this. Create a class Description with name and value attributes:
public class Description {
private String name;
private double val;
}
Now in your CustomClass do this:
public class CustomClass {
List<Description> descriptions;
}
And that's it. Remember to create getters and setters because Jackson needs it.
You could try something like this:
public class DescriptionWrapper {
private Description descriptions;
public Description getDescriptions() {
return descriptions;
}
public void setDescriptions(Description descriptions) {
this.descriptions = descriptions;
}
}
public class Description {
private String desc1;
private NameValue desc2;
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public NameValue getDesc2() {
return desc2;
}
public void setDesc2(NameValue desc2) {
this.desc2 = desc2;
}
}
public class NameValue {
private String name;
private double val;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
}