I cannot create Java Getters and Setters, because I got number(digit) for my Object Key.
I will show you my API response. How can I achieve this without changing the API.
{"api_status": true,
"message": "",
"data": {
"0": {
"id": "aaa",
"name": "aaa",
"address": "aaa",
"category": "aaa",
"open_24_hours": "aaa",
"business_open": "",
"business_close": "",
"type": "0",
"title": null,
"latitude": "6.8729428",
"longitude": "79.8689013",
"city": "",
"distance": "2.95555089735992"
},
"1": {
"id": "bbb",
"name": "bbb",
"address": "bbb",
"category": "bbb",
"open_24_hours": "bbb",
"business_open": "",
"business_close": "",
"type": "0",
"title": null,
"latitude": "6.8767581",
"longitude": "79.8674747",
"city": "",
"distance": "2.915385898910569"
},
}
}
Use the below class and pass it to GSON library with your json data and the Class As a model . you will get your model, each data item is mapped with hashtable where key is your number which i represent as string By iterating over hash map you will get keySet which is your all keys in the data key of json. and for each key you can get itemData.
class JsonStructure{
public boolean api_status;
public String message
HashMap<String,ItemsData> data;
}
class ItemsData{
public String id;
public String name;
public String address;
public String category;
public String open_24_hours;
public String business_open;
public String business_close;
public String type;
public String title;
public String latitude;
public String longitude;
public String city;
public String distance;
}
For retrofit Build
BuildRetrofit(){
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
mConverterFactory = GsonConverterFactory.create();
String baseUrl = "http://dev.appslanka.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(mOkHttpClient)
.addConverterFactory(mConverterFactory)
.build();
mApi = retrofit.create(ApiInterface.class);
}
In ApiInterface define yoyr request method
interface ApiInterface{
#GET("_test/placeInDistance/")
Call<JsonStructure> getResponseForApiCall();
}
Now call this method as retrofit call structure:
Call<JsonStructure> call = mApi.getResponseForApiCall();
Response<JsonStructure> response = call.execute();
Parse this response like below:
HashMap<String, ItemsData> map = response .data;
Set<String> s = map.keySet();
Iterator<String> i = s.iterator();
while (i.hasNext()){
String key = i.next();
ItemsData data = map.get(key);
String id = data.id;
String name = data.name;
String address = data.address;
String category = data.category;
String open24Hr = data.open_24_hours;
String businessOpen = data.business_open;
String close = data.business_close;
String latitue = data.latitude;
..... etc
}
Yes, you can. Use SerializedName annotation like this:
#SerializedName("0")
private MyClass myObject;
Where MyClass is gonna represent a POJO for the data you're getting back.
I just want to note that a better solution would be to change the API (cause this response is weird), to return a list rather than an object with digits for keys, but I can see that you wrote in the question that you cannot change it.
If you really need to parse this JSON. Use custom solution.
For example my solution.
Create class Response with following code :
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class Response {
public boolean apiStatus;
public String message;
public List<Data> datas;
public Response(JSONObject jsonObject) {
apiStatus = jsonObject.optBoolean("api_status");
message = jsonObject.optString("message");
datas = new ArrayList<>();
try {
JSONObject datasJSON = jsonObject.getJSONObject("data");
int index = 0;
while (datasJSON.has(String.valueOf(index))) {
JSONObject dataJSON = datasJSON.getJSONObject(String.valueOf(index));
datas.add(new Data(dataJSON));
index++;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override public String toString() {
return "Response{" +
"apiStatus=" + apiStatus +
", message='" + message + '\'' +
", datas=" + datas +
'}';
}
}
Create class Data with following code :
import org.json.JSONObject;
public class Data {
public String id;
public String name;
public String address;
public String category;
public String open24Hours;
public String businessOpen;
public String businessClose;
public String type;
public String title;
public String latitude;
public String longitude;
public String city;
public String distance;
public Data(JSONObject jsonObject) {
id = jsonObject.optString("id");
name = jsonObject.optString("name");
address = jsonObject.optString("address");
category = jsonObject.optString("category");
open24Hours = jsonObject.optString("open_24_hours");
businessOpen = jsonObject.optString("business_open");
businessClose = jsonObject.optString("business_close");
type = jsonObject.optString("type");
title = jsonObject.optString("title");
latitude = jsonObject.optString("latitude");
longitude = jsonObject.optString("longitude");
city = jsonObject.optString("city");
distance = jsonObject.optString("distance");
}
#Override public String toString() {
return "Data{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
", category='" + category + '\'' +
", open24Hours='" + open24Hours + '\'' +
", businessOpen='" + businessOpen + '\'' +
", businessClose='" + businessClose + '\'' +
", type='" + type + '\'' +
", title='" + title + '\'' +
", latitude='" + latitude + '\'' +
", longitude='" + longitude + '\'' +
", city='" + city + '\'' +
", distance='" + distance + '\'' +
'}';
}
}
Instruction for use this solution:
Response response = new Response(jsonObject);
Instruction for use it, when you use Retrofit2.
For first we need to create custom factory, create class with name ResponseRetrofitConverter, and this following code :
import android.support.annotation.NonNull;
import org.json.JSONObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
public class ResponseRetrofitConverter extends Converter.Factory {
public static ResponseRetrofitConverter create() {
return new ResponseRetrofitConverter();
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new JsonConverter();
}
private final static class JsonConverter implements Converter<ResponseBody, Response> {
#Override
public Response convert(#NonNull ResponseBody responseBody) {
try {
return new Response(new JSONObject(responseBody.string()));
} catch (Exception e) {
return null;
}
}
}
}
When Response is your entity,
Add connect with factory to retrofit use following code line :
.addConverterFactory(ResponseRetrofitConverter.create())
For example my code:
Retrofit.Builder()
.baseUrl(link)
.addConverterFactory(ResponseRetrofitConverter.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
You should create a java List of objects to represent the data.
If you want to bind a Json that has a number as name, and if you are using jackson as json library, you can declare the variable as follow:
#JsonProperty("0")
private CustomObject zero;
#JsonProperty("1")
private CustomObject one;
public CustomObject getZero()
{
return this.zero;
}
public void setZero(CustomObject zero)
{
this.zero= zero;
}
public CustomObject getOne()
{
return this.one;
}
public void setOne(CustomObject one)
{
this.one= one;
}
If you are using Gson then you can use as follows:
public class Model{
#SerializedName("0")
private String object;
}
You can call you class _0, _1... even it's a little bit strange.
Related
When I try to send it, it show me the "error": "Bad Request",
"trace": "...HttpMessageNotReadableException: JSON parse error: Cannot deserialize Map key of type java.time.LocalDate from String "quoteDate": Failed to deserialize java.time.LocalDate"
The JSON I am sending via postman:
{
"stockId":"test3",
"quotes":[
{
"quoteDate":"2003-05-14",
"quoteValue":"35.9"
},
{
"quoteDate":"2016-03-28",
"quoteValue":"55.0"
}
]
}
The controller:
#PostMapping("/stock/save")
public void saveQuotes(#RequestBody StockDTO stockDTO) {
System.out.println(stockDTO.toString());
}
The DTO
public class StockDTO {
String id;
String stockId;
Map<LocalDate, Double> quotes;
}
For quotes, the data type that should come as per the json string is a List. but was given a Map in the DTO.
The DTO should be ::
import com.google.gson.Gson;
import java.util.List;
public class GsonMappingExample {
public static void main(String[] args) {
String jsonString = "{\"stockId\":\"test3\",\"quotes\":[{\"quoteDate\":\"2003-05-14\",\"quoteValue\":\"35.9\"},{\"quoteDate\":\"2016-03-28\",\"quoteValue\":\"55.0\"}]}";
Gson gson = new Gson();
StockDTO stockDTO = gson.fromJson(jsonString, StockDTO.class);
System.out.println(stockDTO);
}
}
class StockDTO {
private final String stockId;
private final List<Quote> quotes;
public StockDTO(String stockId, List<Quote> quotes) {
this.stockId = stockId;
this.quotes = quotes;
}
#Override
public String toString() {
return "StockDTO{" +
"stockId='" + stockId + '\'' +
", quoteList=" + quotes +
'}';
}
}
class Quote {
private final String quoteDate;
private final Double quoteValue;
public Quote(String quoteDate, Double quoteValue) {
this.quoteDate = quoteDate;
this.quoteValue = quoteValue;
}
#Override
public String toString() {
return "Quote{" +
"quoteDate=" + quoteDate +
", quoteValue=" + quoteValue +
'}';
}
}
PS: Here I'm using Gson library to parse the json string, spring-boot automatically does that (using Jackson library I think!)
Request:
{
"name":"iswarya",
"dept":{
"deptName":"eee",
"location":"firstfloor"
},
"additionalDetails":{
"projectName":"finalyearproject"
}
}
Response:
{
"name": "iswarya",
"deptName": null,
"location": null,
"projectName": null
}
Controller class:
#PostMapping(value="/objectMApper")
public String createEmployee(#RequestBody AnnotationTestBean demoEntity) throws JsonProcessingException {
ObjectMapper obj=new ObjectMapper();
return obj.writeValueAsString(demoEntity);
}
In the given example the request for JSON is not wrapped, so its dept and additionalDetails should not be annotated with #JsonUnwrapped.
Instead, a response should be created extending the request class, having a copy constructor, and overriding appropriate getters annotated as #JsonUnwrapped.
The example below uses Lombok annotations to generate getters/setters/constructors.
#Data
#AllArgsConstructor
#NoArgsConstructor
static class Request {
private String name;
private Department dept;
private Details additionalDetails;
}
#Data
static class Department {
private String deptName;
private String location;
}
#Data
static class Details {
private String projectName;
}
static class Response extends Request {
public Response(Request request) {
super(request.name, request.dept, request.additionalDetails);
}
#Override #JsonUnwrapped
public Department getDept() { return super.getDept(); }
#Override #JsonUnwrapped
public Details getAdditionalDetails() { return super.getAdditionalDetails(); }
}
Test
ObjectMapper om = new ObjectMapper();
String json = "{\r\n" +
" \"name\":\"iswarya\",\r\n" +
" \"dept\":{\r\n" +
" \"deptName\":\"eee\",\r\n" +
" \"location\":\"firstfloor\"\r\n" +
" },\r\n" +
" \"additionalDetails\":{\r\n" +
" \"projectName\":\"finalyearproject\"\r\n" +
" }\r\n" +
"}";
Request request = om.readValue(json, Request.class);
Response response = new Response(request);
String str = om.writerWithDefaultPrettyPrinter().writeValueAsString(response);
System.out.println(str);
Output
{
"name" : "iswarya",
"deptName" : "eee",
"location" : "firstfloor",
"projectName" : "finalyearproject"
}
I am currently trying to extract the name 'Best I Ever Had' from the last.fm API shown below using GSON but having difficulty with it constantly returning a null value.
******************************EDIT*****************************
Here is the JSON, with tracks being a list of dictionaries, one for each song name:
{
toptracks: {
track: [
{
name: "Best I Ever Had"
}
]
}
}
Using http://www.jsonschema2pojo.org/ I have created the following classes:
TrackName.java
package com.webservice1;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class TrackName {
#SerializedName("toptracks")
#Expose
private Toptracks toptracks;
public Toptracks getToptracks() {
return toptracks;
}
public void setToptracks(Toptracks toptracks) {
this.toptracks = toptracks;
}
public TrackName withToptracks(Toptracks toptracks) {
this.toptracks = toptracks;
return this;
}
}
Toptracks.java
package com.webservice1;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Toptracks {
#SerializedName("track")
#Expose
private List<Track> track = null;
public List<Track> getTrack() {
return track;
}
public void setTrack(List<Track> track) {
this.track = track;
}
public Toptracks withTrack(List<Track> track) {
this.track = track;
return this;
}
}
and finally Track.java
package com.webservice1;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Track {
#SerializedName("name")
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Track withName(String name) {
this.name = name;
return this;
}
}
And here is my code using fromJson method where reply contains the whole JSON and is constantly 'Track Name com.webservice1.Toptracks#1b9e1916'
String reply;
reply = reader.readLine();
Gson gson = new Gson();
TrackName response = gson.fromJson(reply, TrackName.class);
System.out.println("Track Name " + response.getToptracks());
Any help would be appreciated!!
You can do this using Jackson and I'm sure there are settings to ignore missing fields etc...
Here is the maven dependency for the library
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
String json = "{\n"
+ " \"toptracks\" : {\n"
+ " \"track\": [{\n"
+ " \"name\": \"Best I Ever Had\",\n"
+ " \"playcount\": \"3551414\",\n"
+ " \"listeners\": \"1058277\",\n"
+ " \"mbid\": \"00bde944-7562-446f-ad0f-3d4bdc86b69f\",\n"
+ " \"url\": \"https://www.last.fm/music/Drake/_/Best+I+Ever+Had\",\n"
+ " \"streamable\": \"0\",\n"
+ " \"artist\": {\n"
+ " \"name\": \"Drake\",\n"
+ " \"mbid\": \"b49b81cc-d5b7-4bdd-aadb-385df8de69a6\",\n"
+ " \"url\": \"https://www.last.fm/music/Drake\"\n"
+ " }\n"
+ " }]\n"
+ "}\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
TopTracks track = mapper.readValue(json, TopTracks.class);
List<Object> trackList = (List<Object>)track.toptracks.get("track");
Map<String, Object> trackMap = (Map<String, Object>) trackList.get(0);
System.out.println(trackMap.get("name"));
public class TopTracks {
public Map<String, Object> toptracks = new HashMap<>();
}
Output:
--- exec-maven-plugin:1.2.1:exec (default-cli) # MVN ---
Best I Ever Had
------------------------------------------------------------------------
BUILD SUCCESS
I have a form that should return list of customers.
This form should behave differently in two case:
User starts the research using only "surname"
User starts the research using surname AND name
In the first case the json response has less fields than the response in the second case so I have to ignore all these fields.
I've tried using #JsonInclude(JsonInclude.Include.NON_ABSENT), #JsonInclude(JsonInclude.Include.NON_EMPTY) and #JsonInclude(JsonInclude.Include.NON_NULL) but with each and everyone of these the error returned is always the same:
java.lang.Exception: Could not write content: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"])
This is the pojo Cliente:
package it.gruppoitas.itasacquire.pojo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
#JsonInclude(JsonInclude.Include.NON_ABSENT)
public class Cliente {
#JsonProperty("TIPO_PERSONA")
private String tipoPersona;
#JsonProperty("PRO_CLIE")
private String proClie;
#JsonProperty("CODICE_FISCALE")
private String codiceFiscale;
#JsonProperty("DATA_NASCITA")
private String dataNascita;
#JsonProperty("SESSO")
private String sesso;
#JsonProperty("NOME")
private String nome;
#JsonProperty("COGNOME")
private String cognome;
public String getTipoPersona() {
return tipoPersona;
}
public void setTipoPersona(String tipoPersona) {
this.tipoPersona = tipoPersona;
}
public String getProClie() {
return proClie;
}
public void setProClie(String proClie) {
this.proClie = proClie;
}
public String getCodiceFiscale() {
return codiceFiscale;
}
public void setCodiceFiscale(String codiceFiscale) {
this.codiceFiscale = codiceFiscale;
}
public String getDataNascita() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
Date data = null;
try {
data = sdf.parse(dataNascita);
dataNascita = new SimpleDateFormat("dd/MM/yyyy").format(data);
} catch (ParseException e) {
System.err.println(e);
}
return dataNascita;
}
public void setDataNascita(String dataNascita) {
this.dataNascita = dataNascita;
}
public String getSesso() {
return sesso;
}
public void setSesso(String sesso) {
this.sesso = sesso;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCognome() {
return cognome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
#Override
public String toString() {
return "Cliente [tipoPersona=" + tipoPersona + ", proClie=" + proClie + ", codiceFiscale=" + codiceFiscale + ", dataNascita="
+ dataNascita + ", sesso=" + sesso + ", nome=" + nome + ", cognome=" + cognome + "]";
}}
Any idea?
EDIT: this is an example of the json response structure in case 1
{
"TIPO_PERSONA" : "G",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"PARTITA_IVA" : "123456789",
"SESSO" : "S",
"COGNOME" : "CUSTOMER SRL"
}
And this is an example of the json response in case 2:
{
"TIPO_PERSONA" : "F",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"DATA_NASCITA" : "1969-09-07 00:00:00.0",
"SESSO" : "F",
"NOME" : "Foo",
"COGNOME" : "Fie"
}
As you can see there are less fields in case 1 and STS goes in full-panic mode...
You need to configure your object mapper not to fail on empty beans.
Here is a sample code since you didn't provide the creation of the ObjectMapper code yourself:
private ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
You can also use:
jacksonMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,false);
I have to deserialize following json using Jackson library into Customer class
{
"code":"C001",
"city": "Pune",
"street": "ABC Road"
}
and Classes as
class Address{
String city;
String street;
}
class Customer{
String code;
Address address;
}
I have found similar question on stack
Java jackson embedded object deserialization
but answer does not apply to my case. Also I only want to use Jackson library.
How can I map this json to Customer object?
You can put a #JsonUnwrapped annotation on the Address field in the customer class. Here is an example:
public class JacksonValue {
final static String JSON = "{\n"
+" \"code\":\"C001\",\n"
+" \"city\": \"Pune\",\n"
+" \"street\": \"ABC Road\"\n"
+"}";
static class Address {
public String city;
public String street;
#Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
}
static class Customer {
public String code;
#JsonUnwrapped
public Address address;
#Override
public String toString() {
return "Customer{" +
"code='" + code + '\'' +
", address=" + address +
'}';
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Customer.class));
}
}
Output:
Customer{code='C001', address=Address{city='Pune', street='ABC Road'}}
What you need is a custom deserializer. Jackson How-To: Custom Deserializers
For your use case it could be something like this:
class CustomerDeserializer extends JsonDeserializer<Customer>
{
public Customer deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonNode node = p.getCodec().readTree(p);
String code = node.get("code").asText();
String city = node.get("city").asText();
String street = node.get("street").asText();
Address adr = new Address(city, street);
return new Customer(code, adr);
}
}
Your JSON object for a customer should look like this:
{
"code":"C001",
"address":{
"city": "Pune",
"street": "ABC Road"
}
}
Without some additional transformation this json structure can't be mapped to two classes. Either write a class CustomerAddress that will be having all three fields from json and then create Address getAddress() and Customer getCustomer() in it or transform the json to nest the address information inside the customer field as suggested by #eztam.
public CustomerAddress {
private String code;
private String city;
private String street;
public Address getAddress() {
return new Address(city, street);
}
public Address getCustomer() {
return new Customer(code, this.getAddress());
}
}
Try this !!!
{
"code":"customer1",
"address":{
"type":"nested",
"properties":{
"city":"Hyderabad",
"street":"1000ftRoad"
}
}
}