I need to consume a REST endpoint using spring RestTemplate. Below is the sample response from the endpoint and I need to fetch nested employee json and map it to model object. I found various complex solutions like
Deserializing response to a wrapper object and then fetching Employee object from it
Getting the response as a string and then converting it to json and deserializing
None of these solutions are simple and clean. Not sure if there is a clean and automatic way of doing it like this
ResponseEntity<Employee> response = restTemplate.exchange(URL,..?..);
Response -
{
"employee": {
"id": "123",
"first_name": "foo",
"last_name": "bar"
},
"session": {
"id": "1212121",
"createdDate": "2022-08-18T19:35:30Z"
}
}
Model object -
public class Employee {
private long emplId;
private String fName;
private String lName;
}
RestTemplate can do all of this for you, but first, your object doesn't match your response.
Ideally, you would have a Class that has an Employee and Session, like this
public class HaveSomeClassMan {
private Employee employee;
private Session session;
}
Then you can do
HaveSomeClassMan haveSomeClassMan = restTemplate.postForObject("URL", "requestObject", HaveSomeClassMan.class);
But, since you already have a JSON string, here's how you can convert it to a JSONObject, get the Employee out of it, and use the ObjectMapper to convert it to an Employee object.
JSONObject jsonObject = new JSONObject(s); // where s is your json string
String theEmployee = jsonObject.get("employee").toString(); // get the "employee" json from the JSONObject
Employee employee = new ObjectMapper().readValue(theEmployee, Employee.class); // Use the object mapper to convert it to an Employee
You do need to pay attention, though, as your model doesn't line up with your json. Your model calls the first name field, fname, yet your json has this as first_name. These properties all need to line up. If you want to keep your model as you've defined it, you need to annotate your fields with what their corresponding json field is, like this:
#JsonProperty("first_name")
private String fName;
This tells the mapper to map the JSON field first_name to the property fName. Regardless of whether you let RestTemplate do the mapping for you or you do it manually via the JSONObject approach, your model needs to match your JSON string. You can do this implicitly just by naming the fields in your model the same as they are in your JSON, or explicitly by adding the #JsonProperty to each field that doesn't implicitly map.
Related
I am trying to Map a JSON response to a Java POJO which has a different field name from different API.
I need an efficient way to do this reducing boilerplate codes.
I have tried mapping the JSON property field in Java POJO.
However, the problem is I am fetching data from different sources.
Let's say I have below user class
Class User{
String name;
String contact;
}
The JSON I may receive from different sources can be
{"name": "ABC" , "contact": "123456"}
or
{"userName": "XYZ" , "mobileNo":"4354665"}
There may be more variations as we go on integrating more API's
Is there a way I can archive this?
above is just a simple example
there could be more complex JSON object I may need to read.
like List of User etc.
You can use the #JsonAlias() to give the variable more than one JSON key binding.
#JsonAlias is introduced in Jackson 2.9 release. #JsonAlias defines one or more alternative names for a property to be accepted during deserialization i.e. setting JSON data to Java object. But at the time of serialization i.e. while getting JSON from Java object, only actual logical property name is used and not alias. #JsonAlias is defined as follows.
#Entity
Class User{
#JsonProperty()
#JsonAlias({"name", "userName"})
String name;
#JsonProperty()
#JsonAlias({"contact", "mobileNo"})
String contact;
}
You could use the #JsonSetter annotation like :
public class User{
public String contact;
public String name;
#JsonSetter("name")
public void setName(String name) {
this.name = name;
}
#JsonSetter("userName")
public void setName(String name) {
this.name = name;
}
}
Instead of directly mapping to an entity class , you should have a DTO object or model in between to map the json response. Then, you can convert that into any entity you may choose.If you are fetching the data from different sources , it means you are calling different endpoints, why don't you create different DTO 's for that.In that way even if one of the endpoints introduce a change , it won't affect the rest of the endpoint calls.
Vice-versa you could have different DTO objects being returned from the two endpoints instead of returning the same Entity class as well, that way you can have control over which attributes should be there in the response.
To reduce the boiler plate code, you could use library such as MAP STRUCT to enable conversion between entity and DTO objects easily
Read here about the advantages of using a DTO .
this is one of my REST controller,
#RestController
#RequestMapping("/users/Ache")
public class Users {
#GetMapping
public User getUser() {
User user = new User();
return user;
}
}
As response, Spring boot will translate my Object to JSON,
this is response:
{
"username": "Ache",
"password": "eee",
"token": "W0wpuLAUQCwIH1r2ab85gWdJOiy2cp",
"email": null,
"birthday": null,
"createDatetime": "2019-03-15T01:39:11.000+0000",
"updateDatetime": null,
"phoneNumber": null
}
I want to remove password and token fields, How can I do?
I know two hard ways:
create a new hash map
and add some necessary fields, but it too complex
set those two fields to null
but it still leaves two null valued fields, it is too ugly.
Any better solution?
Spring leverages Jackson library for JSON marshalling by default. The easiest solution that comes to mind is making use of Jackson's #JsonIgnore but that would ignore the property on both serialization and de-serialization. So the right approach would be annotating the field with #JsonProperty(access = Access.WRITE_ONLY).
For instance, inside a hypothetical User class:
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String token;
An alternative would be using #JsonIgnore only on the getter:
#JsonIgnore
public String getPassword() {
return this.password;
}
You can also create another class, for instance UserResponse with all the fields except password and token, and make it your return type. Of course it involves creating an object and populating it, but you leave your User class clean without Jackson annotations, and de-couples your model from your representation.
Keep the getter and setter but add the WRITE_ONLY JsonProperty. This way password validations will work when you use the entity as the request body.
#NotBlank
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
A single JSON is posting from Postman and having two entity names as employee and employee address. From that JSON, empName should be stored in first entity and empAddress should be stored in second entity.
How to do this in Spring Boot? I tried to write only Entities.
First Entity:
class Employee{
private int emId;
private String empName;
//Getters;
//Setter;
}
Second Entity:
class EmployeeAddress{
private int emId;
private String empAddress;
//Getters;
//Setter;
}
My JSON: the firt value in JSON need to be stored in first entity and second need to be stored in second entity. How to do this in Spring Boot from controller?
{
"empName": "sam",
"empAddress:"chennai"
}
You need a DTO.
Something like this:
class Employee{
private String empName;
private String empAddress;
}
So, you can return this object as a Json
I am using Retrofit and Gson to query an API, however I have never come across a JSON response like it.
The Response:
{
Response: {
"Black":[
{"id":"123","code":"RX766"},
{"id":"324","code":"RT344"}],
"Green":[
{"id":"3532","code":"RT983"},
{"id":"242","code":"RL982"}],
"Blue":[
{"id":"453","code":"RY676"},
{"id":"134","code":"R67HJH"}]
}
}
The problem is the list elements id eg "black" is dynamic, so I a have no idea what they will be.
So far I have created a class for the inner type;
class Type {
#SerializedName("id") private String id;
#SerializedName("code") private String code;
}
Is it possible to have the following?
class Response {
#SerializedName("response")
List<Type> types;
}
And then move the list ID into the type, so the Type class would become;
class Type {
#SerializedName("id") private String id;
#SerializedName("code") private String code;
#SerializedName("$") private String type; //not sure how this would be populated
}
If not, how else could this be parsed with just Gson attributes?
Ok so I have just seen this question;
How to parse dynamic JSON fields with GSON?
which looks great, is it possible to wrap a generic map with the response object?
If the keys are dynamic you want a map.
class Response {
#SerializedName("response")
Map<String, List<Type>> types;
}
After deserialization you can coerce the types into something more semantic in your domain.
If this is not suitable you will need to register a TypeAdapter or a JsonDeserializer and do custom deserialization of the map-like data into a simple List.
I have a complex Employee POJO class which has Name, id, salary, dept and Address(vector type) fields.I have generated the json using the jersey restful web service. But the output json does not include the certain pojo fields like address which has datatype as vector. All the fields in POJO class has getter and setters.
Any specific reason that why certain fields are not part of generated JSON ?
//Used below code to generate the JSON
#Path("/employeedDetails")
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public EmployeedBean getDetails(#QueryParam("Id") String Id,
#Context HttpServletRequest servletRequest) {
Employee e1 = new Employee();
// Sets all Name, id, salary, dept and Address
return e1;
}
You are missing code. Can you post what you are returning? If you are returning an array (such as a Java List object), you'll want to wrap that inside another POJO class. I was having the same issue (see RESTful POJO -> JSON mapping not picking up new fields) and this was what was causing it, since the returned JSON was starting with brackets [] instead of {} and therefore JavaScript wasn't parsing it properly. Returning a single object instead of a List fixed the issue.