Retrofit + Gson can not parse simple Map - java

I have a JSON as follows
{
"1.2.3.4": "domain1.com",
"1.2.3.5": "domain2.com"
}
And a model
public class DnsLookup {
#Getter #Setter private Map<String, String> entry;
}
And retrofit query interface
#GET("/dns/")
Call<DnsLookup> getDns(#QueryMap Map<String, String> params);
When I make the query, I get null in dns.
Call<DnsLookup> call = service.getDns(queryMap);
Response<DnsLookup> response;
response = call.execute();
var dns = response.body();
Actually the original JSON is
{
"1.2.3.4": [
"domain1.com", "domain1-1.com"
],
"1.2.3.5": [
"domain2.com"
]
}
And I have tried the following model to only get null. Since the following didn't work, I broke the problem to just key-value pairs. To my surprise, the simple map also didn't work.
public class DnsLookup {
#Getter #Setter private Map<String, List<String>> entry;
}
Am I doing a noob mistake?

Use:
#GET("/dns/")
Call<Map<String, List<String>>> getDns(#QueryMap Map<String, String> params);
Instead of:
#GET("/dns/")
Call<DnsLookup> getDns(#QueryMap Map<String, String> params);

Related

How to parse a json with dynamic property name in java?

I need to consume and parse incoming json from a CRM system in my code. I used RestTemplate to do it. So the response from the CRM system looks like below.
{ "GvyArEFkg6JX6wI": {
"entityId": "GvyArEFkg6JX6wI",
"mergePolicy": {
"id": "9245a39d-fe1a-4b33-acab-9bc5cbabf37c"
}
}
}
Now the problem is the property name ("GvyArEFkg6JX6wI" in this case) in dynamic and in the next response it would be another string. In this case, how can I parse this json as this is not fixed? I tried using jsonGetter but it only wraps it around another block and still does not resolve the problem of parsing the response.
AdobeResponseDto.class
#Builder
#ToString
#AllArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
#NoArgsConstructor
public class AdobeResponseDto {
public Map<String, AdobeResponseFinal> adobeResponseWrapper = new HashMap<>();
#JsonAnySetter
public void setAdobeResponseWrapper(String name, AdobeResponseFinal value) {
adobeResponseWrapper.put(name, value);
}
#JsonAnyGetter
public Map<String, AdobeResponseFinal> getAdobeResponseWrapper() {
return adobeResponseWrapper;
}
}
AdobeResponseFinal.class
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class AdobeResponseFinal {
public String entityId;
public MergePolicy mergePolicy;
}
I am calling the service like this:
final ResponseEntity<AdobeResponseDto> finalResponse;
finalResponse = aepClient.exchange(uri,HttpMethod.GET,entity,AdobeResponseDto.class);
final AdobeResponseDto body = finalResponse.getBody();
if(ObjectUtils.isNotEmpty(body)){
return body;
}
But in this way, the response I am getting is
{
"adobeResponseWrapper": {
"GvyArEFkg6JX6wI": {
"entityId": "GvyArEFkg6JX6wI",
"mergePolicy": {
"id": "9245a39d-fe1a-4b33-acab-9bc5cbabf37c"
}
}
If you just want to get the value of dynamic field name (e.g., "GvyArEFkg6JX6wI" in your case), you can just deserialize the response body to a Map and then traverse its value as follows:
ResponseEntity<String> finalResponse = aepClient.exchange(uri, HttpMethod.GET, entity, String.class);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> result = objectMapper.readValue(finalResponse.getBody(), Map.class);
result.values().forEach(System.out::println);
Console output:
{entityId=GvyArEFkg6JX6wI, mergePolicy={id=9245a39d-fe1a-4b33-acab-9bc5cbabf37c}}
And if you want to deserialize the response body to your DTO (I assume that there is only ONE root-level property), you can modify the DTO like:
public class AdobeResponseDto {
private AdobeResponseFinal adobeResponseFinal;
#JsonAnySetter
public void setAdobeResponseFinal(String name, AdobeResponseFinal value) {
adobeResponseFinal = value;
}
#JsonAnyGetter
public AdobeResponseFinal getAdobeResponseFinal() {
return adobeResponseFinal;
}
}
Then you can get similar result as follows:
ResponseEntity<String> finalResponse = aepClient.exchange(uri, HttpMethod.GET, entity, String.class);
AdobeResponseDto adobeResponseDto = objectMapper.readValue(finalResponse.getBody(), AdobeResponseDto.class);
System.out.println(adobeResponseDto.getAdobeResponseFinal().toString());
Console output:
AdobeResponseFinal{entityId='GvyArEFkg6JX6wI', mergePolicy=MergePolicy{id='9245a39d-fe1a-4b33-acab-9bc5cbabf37c'}}

Return Hashmap from Spring Rest API

I want to use this code to get some data from Rest API:
public Map<Integer, String> getCategoriesList() {
Map<Integer, String> list = new HashMap<>();
list.put(1, "Electronics");
list.put(2, "Outdoor and Sports");
list.put(3, "Home and Garden");
list.put(4, "Home appliances");
list.put(5, "Air conditioners and heaters");
list.put(6, "IT accessories");
list.put(7, "Photo and Video");
list.put(8, "TV Video and Gaming");
return list;
}
#GetMapping("categories")
public ResponseEntity<List<String>> getCategoriesList() {
return (ResponseEntity<List<String>>) categoriesService.getCategoriesList();
}
I get error: class java.util.HashMap cannot be cast to class org.springframework.http.ResponseEntity
What is the appropriate way to return this data as a response?
You cannot cast one type to another like that...try this
ResponseEntity type:
#GetMapping("categories")
public ResponseEntity<Map<Integer, String>> getCategoriesList() {
return new ResponseEntity<Map<Integer,String>>(categoriesService.getCategoriesList(), HttpStatus.OK);
}
Without ResponseEntity wrapper
#GetMapping("categories")
#ResponseStatus(code = HttpStatus.OK)
public Map<Integer, String> getCategoriesList() {
return categoriesService.getCategoriesList();
}
Since both types of map are known to jackson (I presume that's what you are using in spring for serialization/deserialization), no need to do anything more.
Reference:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

How to get specific variable from JSON object?

I'm trying to get some data from other API and I need to get StatusCode from JSON object but I'm getting null object.
I was trying to create new class with StatusCode variable but I'm getting null.
I'm trying to get this data :
Data(data=[{"Number":"20450143160505","DateCreated":"11-06-2019 10:14:27","DocumentWeight":0.5,"CheckWeight":0,"SumBeforeCheckWeight":0,"PayerType":"Recipient","RecipientFullName":"","RecipientDateTime":"","ScheduledDeliveryDate":"12-06-2019","PaymentMethod":"Cash","CargoDescriptionString":"","CargoType":"Parcel","CitySender":"Сокільники","CityRecipient":"Київ","WarehouseRecipient":"Відділення №150 (до 30 кг): вул. Антоновича, 43 (м.\"Олімпійська\")","CounterpartyType":"PrivatePerson","Redelivery":1,"RedeliverySum":"","RedeliveryNum":"","RedeliveryPayer":"","AfterpaymentOnGoodsCost":"","ServiceType":"WarehouseWarehouse","UndeliveryReasonsSubtypeDescription":"","WarehouseRecipientNumber":150,"LastCreatedOnTheBasisNumber":"","LastCreatedOnTheBasisDocumentType":"","LastCreatedOnTheBasisPayerType":"","LastCreatedOnTheBasisDateTime":"","LastTransactionStatusGM":"","LastTransactionDateTimeGM":"","WarehouseRecipientInternetAddressRef":"916c7c93-8460-11e4-acce-0050568002cf","MarketplacePartnerToken":"","DateScan":"12:23 12.06.2019","ClientBarcode":"","SenderAddress":"","RecipientAddress":"","CounterpartySenderDescription":"","CounterpartyRecipientDescription":"","CounterpartySenderType":"Organization","PaymentStatus":"","PaymentStatusDate":"","AmountToPay":"","AmountPaid":"","WarehouseRecipientRef":"916c7c94-8460-11e4-acce-0050568002cf","DocumentCost":40,"AnnouncedPrice":"","OwnerDocumentNumber":"","DateFirstDayStorage":"2019-06-21","InternationalDeliveryType":"","DaysStorageCargo":"","RecipientWarehouseTypeRef":"841339c7-591a-42e2-8233-7a0a00f0ed6f","StorageAmount":"","StoragePrice":"","VolumeWeight":"0.50","SeatsAmount":"1","OwnerDocumentType":"","ActualDeliveryDate":"2019-06-12 12:23:22","DateReturnCargo":"","CardMaskedNumber":"","Status":"Прибув у відділення","StatusCode":"7","RefEW":"8ed817ef-8c18-11e9-91ff-0025b501a04b","RedeliveryPaymentCardRef":"","RedeliveryPaymentCardDescription":"","CreatedOnTheBasis":"","DatePayedKeeping":"2019-06-21 00:00:00","OnlineCreditStatusCode":"","OnlineCreditStatus":""}])
Method to get data :
RestTemplate restTemplate = new RestTemplate();
Data string = restTemplate.postForObject(blalba,blabla,Data.class)
And my class
public class Data {
#JsonProperty("data")
private JsonNode data;
//get set
}
There are a few ways to achieve it:
Using a Map<String, Object>
You could read the response payload as a Map<String, Object>:
ParameterizedTypeReference<HashMap<String, Object>> responseType =
new ParameterizedTypeReference<HashMap<String, Object>>() {};
Map<String, Object> responsePayload =
restTemplate.exchange(purchaseRequestDetailsEndpoint, HttpMethod.POST,
new HttpEntity<>(requestPayload), responseType);
String statusCode = responsePayload.get("StatusCode");
Mapping only the properties you need
Define a class mapping the properties you need:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class ResponsePayload {
#JsonProperty("StatusCode")
private String statusCode;
}
And read the response payload using the class defined above:
ResponsePayload responsePayload =
restTemplate.postForObject(uri, request, ResponsePayload.class);
String statusCode = responsePayload.getStatusCode();
Alternatively you could map the properties you need and store the rest in a map:
#Data
public class ResponsePayload {
#JsonProperty("StatusCode")
private String statusCode;
#JsonAnySetter
private Map<String, Object> properties = new HashMap<>();
#JsonIgnore
public Object get(String key) {
return properties.get(key);
}
}

Using Retrofit and Gson with an api that returns dynamic top level JSON

Im using Retrofit to access the following api:
https://api.nasa.gov/neo/rest/v1/feed?api_key=DEMO_KEY
The near_earth_objects object contains multiple arrays each with a key representing a date. This value obviously changes if you access a different date.
As usual, I defined my POJOs according to the returned JSON structure. The following is my main response class:
public class AsteroidResponse {
private Links links;
#SerializedName("element_count")
private Integer elementCount;
#SerializedName("near_earth_objects")
private NearEarthObjects nearEarthObjects;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
//getters and setters
}
The NearEarthObjects class looks like the following:
public class NearEarthObjects {
private List<Observation> observation = new ArrayList<>();
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
//getters and setters
}
I've run into this issue before, and was able to use a Map<String, SomeCustomModel> to get it to automatically parse and set the the date as a key in the map. This method is suggested in a few answers around SO.
I tried to do the same in this situation, replacing the aforementioned class to look like this:
public class NearEarthObjects {
private Map<String, Observation> observation = new HashMap<>();
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
}
Unfortunately, this time around this method does not seem to be working as expected. The map is being returned empty. What might be the issue? What would be the best way to structure my models to properly have the returned JSON parsed?
I think the easy way to parse your Json data inside object near_earth_objects that structuring your json returned like that:
"near_earth_objects":[
{
"date":"2016-11-07",
"data":[
{
"links":{
"self":"https://api.nasa.gov/neo/rest/v1/neo/3758255?api_key=DEMO_KEY"
},
"neo_reference_id":"3758255",
"name":"(2016 QH44)",
"nasa_jpl_url":"http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=3758255",
"absolute_magnitude_h":22.381,
"estimated_diameter":{
"kilometers":{
"estimated_diameter_min":0.0887881438,
"estimated_diameter_max":0.1985363251
},
"meters":{
"estimated_diameter_min":88.7881437713,
"estimated_diameter_max":198.5363250687
},
"miles":{
"estimated_diameter_min":0.0551703777,
"estimated_diameter_max":0.1233647148
},
"feet":{
"estimated_diameter_min":291.2996936107,
"estimated_diameter_max":651.3659167384
}
},
"is_potentially_hazardous_asteroid":false,
"close_approach_data":[
{
"close_approach_date":"2016-11-07",
"epoch_date_close_approach":1478505600000,
"relative_velocity":{
"kilometers_per_second":"9.9505291907",
"kilometers_per_hour":"35821.9050865416",
"miles_per_hour":"22258.3387466903"
},
"miss_distance":{
"astronomical":"0.1045395934",
"lunar":"40.6659011841",
"kilometers":"15638901",
"miles":"9717563"
},
"orbiting_body":"Earth"
}
]
},
{
"links":{
"self":"https://api.nasa.gov/neo/rest/v1/neo/3758255?api_key=DEMO_KEY"
},
"neo_reference_id":"3758255",
"name":"(2016 QH44)",
"nasa_jpl_url":"http://ssd.jpl.nasa.gov/sbdb.cgi?sstr=3758255",
"absolute_magnitude_h":22.381,
"estimated_diameter":{
"kilometers":{
"estimated_diameter_min":0.0887881438,
"estimated_diameter_max":0.1985363251
},
"meters":{
"estimated_diameter_min":88.7881437713,
"estimated_diameter_max":198.5363250687
},
"miles":{
"estimated_diameter_min":0.0551703777,
"estimated_diameter_max":0.1233647148
},
"feet":{
"estimated_diameter_min":291.2996936107,
"estimated_diameter_max":651.3659167384
}
},
"is_potentially_hazardous_asteroid":false,
"close_approach_data":[
{
"close_approach_date":"2016-11-07",
"epoch_date_close_approach":1478505600000,
"relative_velocity":{
"kilometers_per_second":"9.9505291907",
"kilometers_per_hour":"35821.9050865416",
"miles_per_hour":"22258.3387466903"
},
"miss_distance":{
"astronomical":"0.1045395934",
"lunar":"40.6659011841",
"kilometers":"15638901",
"miles":"9717563"
},
"orbiting_body":"Earth"
}
]
}
]
}
// The other objects
]
if you want to parse dynamic key. You can use this link:
How to parse a dynamic JSON key in a Nested JSON result?

Spring RequestParam Map<String, String>

I have this in my controller:
#RequestMapping(value = "/myUrl", method = RequestMethod.GET)
public String myUrl(#RequestParam(value = "test") Map<String, String> test)
{
return test.toString();
}
And I'm making this HTTP request:
GET http://localhost:8080/myUrl?test[a]=1&test[b]=2
But in the logs I'm getting this error:
org.springframework.web.bind.MissingServletRequestParameterException: Required Map parameter 'test' is not present
How can I pass Map<String, String> to Spring?
May be it's a bit late but this can be made to work by declaring an intermediate class:
public static class AttributeMap {
private Map<String, String> attrs;
public Map<String, String> getAttrs() {
return attrs;
}
public void setAttrs(Map<String, String> attrs) {
this.attrs = attrs;
}
}
And using it as parameter type in method declaration (w/o #RequestParam):
#RequestMapping(value = "/myUrl", method = RequestMethod.GET)
public String myUrl(AttributeMap test)
Then with a request URL like this:
http://localhost:8080/myUrl?attrs[1]=b&attrs[222]=aaa
In test.attrs map all the attributes will present as expected.
It's not immediately clear what you are trying to do since test[a] and test[b] are completely unrelated query string parameters.
You can simply remove the value attribute of #RequestParam to have your Map parameter contain two entries, like so
{test[b]=2, test[a]=1}

Categories

Resources