I am trying to consume data from some public API but I am stuck on some issue about the design of this data. I am using Spring.
I am doing it with the classic way :
private ResponseEntity<ApiResponse> getVehicules() {
final String methodUri = "/vehicules";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
return restTemplate.exchange(apiUri + methodUri, HttpMethod.GET, entity, ApiResponse.class);
}
The problem is more about the design of ApiResponse class. Indeed, the api returned json looks like :
{
"status": "ok",
"meta": {
"count": 2,
"page_total": 1,
"limit": 100,
"page": null
},
"data": {
"18497": {
"id": 18497,
"name": "vehiculeName",
"nation": "vehiculeNation"
},
"52467": {
"id": 52467,
"name": "anotherVehiculeName",
"nation": "anotherVehiculeNation"
}
}
}
So how do I manage the id (The one before the brackets - ex 18497:{...} ) in my class ?
I am stuck trying to create a Data class because of that and as you can imagine, its a public API not mine, so I can't change anything on this side.
This kind of weird Json (using dynamic information in field names) is tricky to figure out how to read. But the solution is simple.
You will need to use a Map on your ApiResponse class, like:
class ApiResponse {
// other fields
#JsonProperty("data")
private Map<String, VehicleResponse> vehicles;
}
The VehicleResponse is a normal class with id, name, nation fields.
As result, the map vehicles will have as key the 18497 (and etc) and as value the information about the vehicle (id, name, nation) in VehicleResponse.
The easiest and fastest way to build java classes from JSON is by using a schema mapper like this one:
http://www.jsonschema2pojo.org/
It generates java classes directly from JSON and also allows you to specify many different options such as the annotation types that should be generated.
Related
I have got two main model classes: Customer and Product
public class Customer {
String name;
String surname;
int age;
BigDecimal cash;
}
public class Product {
String name;
Category category;
BigDecimal price;
}
I want to build json file with Map<Customer, List<Product>>
When I write to json file data with my method which works correct - I am sure about this - the json file shows this syntax
{
"Customer{name\u003d\u0027Custo1\u0027, surname\u003d\u0027Surname\u0027, age\u003d18, cash\u003d1200}": [
{
"name": "prod1",
"category": "CLOTHES",
"price": 12000
},
{
"name": "prod2",
"category": "ELECTRONIC",
"price": 15000
}
]
}
Then when i want to read this file, the error Exception in thread "main" java.util.NoSuchElementException: No value present occurs so I think that the Customer syntax from json file is not recognized.
So I tried to write data to json file on my own with this syntax below, but it does not work
[
{
"name": "Abc",
"surname": "Def",
"age": 14,
"cash": "2000"
}
:
[
{
"name": "prod1",
"category": "CLOTHES",
"price": 12000
},
{
"name": "prod2",
"category": "ELECTRONIC",
"price": 15000
}
]
]
json converter method:
public void toJson(final T item) {
try (FileWriter fileWriter = new FileWriter(jsonFilename)) {
fileWriter.write(gson.toJson(item));
} catch (Exception e) {
throw new ValidatorException(e.getMessage());
}
}
#Tom is right on the issues you've faced with. I'll explain why and suggest one more solution.
Your first JSON is technically a valid JSON but it cannot be deserialized, because the map keys are results of the Customer.toString() method Gson uses by default. This is why it looks weird, acts like a debug string, and can't be deserialized back: there it is almost always no way to restore an object from the toString() result (toString is designed mostly for debugging/logging purposes providing basic information regarding the state of a particular object that does not need to expose its all internals at all).
Your second JSON is invalid JSON. Period.
Tom's suggestion of making the list of products a part of the customer class is totally fine. Having it implemented like that lets you to serialize everything as a list like this:
[
{
"name": "john",
"products": [
{"name": "prod1"},
{"name": "prod2"}
]
}
]
Hint: separating domain objects (Customer and Product) and representation objects for data transfer (CustomerDto and ProductDto) is usually a fine idea too since it allows to create representation for any concrete representation implementation (one for various JSON implementation libraries, two for other-format-oriented tools, third for persistence, four for UI views, etc), so it might be implemented like converting Map<Customer, List<Product>> to List<CustomerDto> and back (possibly by using mapper-generators like MapStruct).
If for whatever reason it is not possible to reorganize your domain classes or create Gson-friendly DTO-mappings, or you're fine to keep it as simple as possible and you're fine with having not that trivial JSON structure (as long as you understand implications of the format in this solution: evolution, distribution, etc), then you can enable special Gson mode to support this kind of maps. It generates valid JSONs that can be serialized and deserialized back, but the way it is implemented looks a bit of anti-pattern to me because of losing semantics due to using arrays as the data container.
#AllArgsConstructor
#EqualsAndHashCode
#ToString
final class Customer {
final String name;
}
#AllArgsConstructor
#EqualsAndHashCode
#ToString
final class Product {
final String name;
}
public final class MapTest {
private static final Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.create();
private static final TypeToken<Map<Customer, List<Product>>> customerToProducts = new TypeToken<Map<Customer, List<Product>>>() {};
#Test
public void test() {
final Map<Customer, List<Product>> ordersBefore = ImmutableMap.of(
new Customer("john"), ImmutableList.of(new Product("prod1"), new Product("prod2"))
);
final String json = gson.toJson(ordersBefore, customerToProducts.getType());
Assertions.assertEquals("[[{\"name\":\"john\"},[{\"name\":\"prod1\"},{\"name\":\"prod2\"}]]]", json);
final Map<Customer, List<Product>> ordersAfter = gson.fromJson(json, customerToProducts.getType());
Assertions.assertEquals(ordersBefore, ordersAfter);
}
}
Note that it generates JSON like this (index 0 means the key, index 1 means the value):
[
[
{"name": "john"},
[
{"name": "prod1"},
{"name": "prod2"}
]
]
]
Need to map multiple types of JSON responses to a single POJO so that I can compare the different objects to provide insight about the differences.
I had tried mapping the first response to the POJO and parsed the second response to populate the defined POJO:
class XXX {
#JsonProperty("accountHolder")
private String accountHolder;
#JsonProperty("routingNumber")
private String routingNumber;
#JsonProperty("balance")
private List<Balance> balance;
#JsonProperty("accountName")
private String accountName;
#JsonProperty("bankTransferCodeType")
private String bankTransferCodeType;
#JsonProperty("individualInformation")
private IndividualInformation individualInformation;
#JsonProperty("acctType")
private String acctType;
#JsonProperty("transactionList")
private TransactionList transactionList;
#JsonProperty("accountNumber")
private String accountNumber;
#JsonProperty("uniqueId")
private String uniqueId;
#JsonProperty("bankNetID")
private String bankNetID;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
}
First response:
[
{
"ACCOUNT_NAME": "",
"ACCOUNT_NUMBER": "",
"AVAILABLE_BALANCE": null,
"CURRENT_BALANCE": "",
"FULL_ACCOUNT_NUMBER": null,
}
]
Second response:
"bankAccount": [
{
"accountName": "",
"accountNumber": "",
"routingNumber": "",
"fullAccountNumber": "",
"bankTransferCodeType": "",
"acctType": "",
"transactionList": {
"transaction": [
{
"amount": {
"curCode": "",
"content": ""
}
],
"oldestTxnDate": ""
},
"uniqueId":
}
}
Expecting a generic way to map the different structured JSON entities to single POJO.
How to map multiple JSON responses to a single Java POJO?
As both responses seem to be completely different from each other, with nothing in common, I would refrain from attempting to use a single class for reading both responses.
Expecting a generic way to map the different structured JSONs to single POJO.
You could parse both responses as a Map<String, Object> and then map the values to a common class.
You could create separated classes for mapping each response. It will allow you to decouple them and evolve them as you need. You also can use use mapping frameworks such as MapStruct for reducing the boilerplate code when mapping from one object to another.
It doesn’t seems to have any generic way. But you can do this:
Create multiple domain classes for each response type
Create a single standard domain class
Create mapper for each response class to map that to standard domain
class. You can use MapStruct reference here
I would suggest using Jackson Json Views. Here is an example for the same :
Example
public class Views {
public class Global {
}
public class Internal extends Global {
}
}
class XXX {
#JsonView(Views.Global.class)
#JsonProperty("accountHolder")
private String accountHolder;
#JsonView(Views.Internal.class)
#JsonProperty("routingNumber")
private String routingNumber;
}
Hope it helps.
What I did is I created a MyResponse model containing basically all response fields from the JSON response you expect to get.
MyResponse has c-tor or receiving these fields or setters allowing setting them.
Then I created some kind of service class MyService that can issue multiple requests and gets responses back.
Then you just do something like this in some kind of manager class or whatever you call it:
MyService mySer = new MyService();
MyResponse myRes = new MyResponse(
mySer.getDetails(),
mySer.getPicture(),
mySer.getSomethingElse()
);
These calls (getDetails, getPicture...) send requests to end point and return responses which are then just mapped into the the fields of MyResponse class constructor. This happens by the framework so MyResponse has annotations #XmlRootElement and #XmlAccessorType of type FIELD to ensure that happens.
If for whatever reason, you dont want to create response containing result of getPicture for example, you just assign null to that imput parameter.
I suggest to use #JsonProperty("") and #JsonAlias("").
class XXX {
#JsonAlias("accountName")
#JsonProperty("ACCOUNT_NAME")
private String name;
#JsonAlias("routingNumber")
#JsonProperty("ROUTING_NUMBER")
private String routing;}
I hope it helps.
I am currently writing a SpringBoot application that retrieves a JSON array from an external API. The part of the JSON I need looks like:
{
"users": [
"id": 110,
"name": "john"
]
}
In my Controller I am doing the following:
ResponseEntity<Users> response = restTemplate
.exchange(url, headers, Users.class);
return response
I then have a Users class that looks like:
#JsonProperty("id")
public String id;
#JsonProperty("name")
public string name;
How can I access the information inside the JSON array?
Thanks in advance.
Instead of loading into a POJO based on your return type you need to accept list of Users.
You cannot accept List of user class in ResponseEntity you need to first cast into object class.
ResponseEntity<Object> response = restTemplate .exchange(url, headers, Object.class);
Then you need to convert it into list of users.
List<Users> usersList = (List<Users>) response.getBody();
The JSON you posted above is not correct. It needs to be:
{
"users": [
{
"id": 110,
"name": "john"
}
]
}
and whatever object is used needs a list of Users.
The other thing is you restTemplate call is wrong, you are expecting the call to return ResponseEntity<Opportunities> class yet when in your restTemplate you are giving it the User class and that will return ResponseEntity<User> instead
I am getting the below JSON response format from a third party web service:
{
"meta": {
"code": 200,
"requestId": "1"
},
"response": {
"locations": [
{
"id": "1",
"name": "XXX",
"contact": {
phone: '123',
email: 'abc'
},
"location": {
"address": [
"Finland"
]
}
},
{
// another location
}
]
}
}
And here is what I should return as a response from my own web service:
[
{
"id": "1",
"name": "XXX",
"phone": '123',
"address": "Finland"
},
{
// another location
}
]
What should I do? I've read some good stuff about Jackson but there are only a few simple examples where you map some simple JSON obj as is to POJO. In my case, I need to remove a few nodes, and also traverse deeper down the hierarchy to get the nested value. This is my baby step so far in my spring boot app:
#GET
#Path("{query}")
#Produces("application/json")
public String getVenues(#PathParam("query") String query){
return client.target(url).queryParam("query",query).request(...).get(String.class)
}
Any helps, pointers, recommendations are welcomed!
You are using JAX-RS annotations instead of the Spring web service annotations. You can make this work, but I would recommend going with the default Spring annotations because those are all autoconfigured for you if you're using the spring boot starter dependencies.
First thing - you need to create classes that are set up like the request and response. Something like this:
public class ThirdPartyResponse {
MetaData meta;
Response response;
}
public class Response {
List<Location> locations;
}
public class MetaData {
String code;
String requestId;
}
public class Location {
String id;
String name;
Contact contact;
LocationDetails location;
}
public class Contact {
String phone;
String email;
}
public class LocationDetails {
List<String> address;
}
You can use Jackson annotations to customize the deserialization, but by default it maps pretty logically to fields by name and the types you might expect (a JSON list named "locations" gets mapped to a List in your object named "locations", etc).
Next you'll want to use a #RestController annotated class for your service, which makes the service call to the third party service using RestTemplate, something like:
#RestController
public class Controller {
#Value("${url}")
String url;
#RequestMapping("/path"
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public List<Location> locations(#RequestParam String query) {
// RestTemplate will make the service call and handle the
// mapping from JSON to Java object
RestTemplate restTemplate = new RestTemplate();
ThirdPartyResponse response = restTemplate.getForObject(url, ThirdPartyResponse.class);
List<Location> myResponse = new List<>();
// ... do whatever processing you need here ...
// this response will be serialized as JSON "automatically"
return myResponse;
}
}
As you can see, Spring Boot abstracts away a lot of the JSON processing and makes it pretty painless.
Take a look at Spring's guides which are pretty helpful:
Consuming a service with RestTemplate
http://spring.io/guides/gs/consuming-rest/
Creating a web service using #RestController
https://spring.io/guides/gs/rest-service/
This can be done using google api JacksonFactory.
Below is my suggested solution for this.
First you should create a pojo class corrosponding to the json data you are recieving and the json data to which you are trying to convert.
Use google api client to map the keys to the pojo.
Below is the pojo classes corrosponding to the json data you are recieving.
import com.google.api.client.util.Key;
Class Response{
#Key("locations")
List<FromLocations> fromLocations;
}
import com.google.api.client.util.Key;
Class FromLocations
{
#Key("id")
String id;
#Key("name")
String name;
#Key("contact")
Contact contact;
#Key("location")
Location location;
}
Here Contact and Loaction will be a another classes using the same strategy;
Below is the pojo corrosponding to the json to which you want to convert.
Class ToLocations{
String id;
String name;
String phone;
String address;
}
Now you can parse the requset containing the json objec to the fromLocations class as below.
String responseMeta = response.parseAsString();
JSONObject queryJsonObject = new JSONObject(responseMeta);
if(queryJsonObject.has("locations")){
Response response = JacksonFactory.getDefaultInstance().fromString(responseMeta,Response.class);
List<FromLocations> fromLocationsList = response.getFromLocations();
}
Next step is to iterate the list fromLocationsList and get the desired values from each FromLocations object and add it to the ToLocations object.
Next the ToLocations object can be add it to a list and convert it to json.
I have the following controller:
#RequestMapping(value = "/test/inst/", method = RequestMethod.POST)
public #ResponseBody ResponseDTO<InstDTO>
testPostREST(#RequestBody RequestDTO<InstDTO> instDTO) {
RequestDTO<InstDTO> dto = new RequestDTO<InstDTO>();
ResponseDTO<InstDTO> responseDto = new ResponseDTO<InstDTO>();
responseDto.setPayload(instDTO.getPayload());
return responseDto;
}
with the following request object:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class RequestDTO<T> {
private List<T> payload;
public RequestDTO() {
System.out.println("constructor");
}
public RequestDTO(List<T> payload) {
this.payload = payload;
}
public List<T> getPayload() {
return payload;
}
public void setPayload(List<T> payload) {
this.payload = payload;
}
}
When the POST comes through and I look the object I get, the payload list has LinkedHashMap objects instead of objects of my DTO type.
How can I make spring+jackson convert the JSON into my DTO object. Bear in mind that I plan to reuse the wrapper ResponseDTO for other lists of objects and that's why I'm using a generic list (List).
Here's the JSON I'm trying.
{
"payload": [
{
"d": "Test 0",
"id": "abcde",
"c": "Test 0"
},
{
"d": "Test 1",
"id": "123",
"c": "Test 1"
}
]
}
Your code does not work due to type erasure. Jackson runtime does not know that you are trying to marshal the payload into InstitutionDTO objects (since this information has been erased at compile time). When Jackson looks at the payload, it sees valid JSON objects on the wire and a List<Object> on the Java side. It has no choice but to map the payload to a Map implementation, which in your case seems to be LinkedHashMap.
You may be able to achieve what you are looking for as follows:
class RequestDTO<T> {}
class ResponseDTO<T> {}
class InstitutionDTO {}
class InstitutionRequestDTO extends RequestDTO<InstitutionDTO>
class InstitutionResponseDTO extends ResponseDTO<InstitutionDTO>
#RequestMapping(value = "/test/institution/", method = RequestMethod.POST)
public #ResponseBody InstitutionResponseDTO
testPostREST(#RequestBody InstitutionRequestDTO institutionDTO) { }
Disclaimer: I haven't tried this code myself but most of my applications have code similar to this and it works with Jackson, Castor, Atom, etc. without a glitch.
If using Jackson isn't a requirement then you might want to consider using Gson instead.
You can tell Gson how to deserialize your json by just passing it a json string and the class that you want to deserialize. It would be pretty simple. I've included an example where I had to deserialize a response from a rest api below.
entity = response.getEntity();
jsonResponse = EntityUtils.toString(entity);
JsonNode root = mapper.readTree(jsonResponse).get("result");
returnedProduct = gson.fromJson(root.toString(),Product.class);