how to serialize multi-level json using jackson in spring? - spring

This is the response body that is expected out the endpoint /foo/bar.
As you can see, this is nested json and hence for each level do I require a Java object. what is the efficient way to construct this?. I am using Spring 4.1 which in turn uses Jackson to serialize POJOs.
{
"user": {
"status": {
"state": "active",
"message": [
"registered on 01.10.2015"
]
}
},
"features": {
"xbox": {
"state": "enabled",
"message": [
"foo",
"bar"
]
},
"playstation": {
"state": "disabled",
"message": [
"hello ",
"world"
]
}
}
}
Here is something I have thought
#RequestMapping("foo/bar")
#ResponseBody
public Result getData(long id){
Result result = getUserData(id);
return result;
}
public class Result {
private User user;
private List<Feature> features;
//getters and setters
}
public class Status {
private State state;
private Message messages;
//getters and setteers
}
public class State {
private String state;
//getters and setters
}
public class Message {
private List<String> messages;
//getters and setters
}
Similarly for "features" node, I will construct Java POJO for each level of Json object. I am aware of using objectMapper and JsonNode but wondering if it is possible to effectively construct nested json using java objects.

Classes like State could be an enum, if you know for sure there will only be a limited domain of values. And Message shouldn't be a separate class- just embed List<String> message directly in Status.
If you want a more compact representation, you can always provide your own serializer/deserializer classes to customise the translation.

Related

How to Enable/Disable Entity Relations in Runtime?

I have a Spring Boot app that has basic CRUD services. For the read services I want to see their relations as well. There is no problem for implementing relations by #ManyToOne, #OneToOne, etc. annotations like this example.
My problem is I want to enable this relations based on a parameter in list service or I could use another endpoint as well. How can I achieve this? Any suggestions are welcome.
parameter version could be like ->
/employe/list?includeRelations=true
endpoint version could be like ->
/employee/list/byRelations
My entities are like;
#Entity
#Table(name = "employee")
public class Employee{
private long id;
private String name;
private Address address;
// getter setters
}
#Entity
#Table(name = "address")
public class Address {
private long id;
private String name;
private String postalCode;
// getter setters
}
EDIT
e.g.
without includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
}
with includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
"address": {
"id":1,
"name": "HOME",
"postalCode": "11111"
}
}
its some sudo code for your understanding . you can use Query Parameter and In Condition you call repo what you want :
for my scenario i want different response short, medium and long
#RequestMapping(value = "/getContacts", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json")
public String getContact(#RequestBody ContactItemRequestInfo contactItemRequestInfo,
#RequestParam(required = false) String key,
String Contact)
{
if(key.equals("medium"))
{
return Contact="{\"responseCode\":\"02\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else if(key.equals("long"))
{
return Contact="{\"responseCode\":\"03\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else
{
return Contact="{\"responseCode\":\"00\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
}
It will be helpful for you !!
One of the ways would be to have different data transfer objects to return, depending on the REST request.
Let's assume you have the following classes, apart from entities.
class EmployeeDto {
private Long id;
private String name;
}
class EmployeeAddressDto {
private Long id;
private String name;
private AddressDto address;
}
class AddressDto {
private Long id;
private String name;
private int postalCode;
}
Then in a controller you would do something like this.
#GetMapping("/employee/list")
public ResponseEntity<?> getEmployees(#RequestParam int detailed) {
if (detailed) {
return employeeService.getDetailedEmployeeList();
} else {
return employeeService.getEmployeeList();
}
}
Service inteface would look like this.
interface EmployeService() {
List<EmployeeDto> getEmployeeList();
List<EmployeeAddressDto> getDetailedEmployeeList();
}
You would also need to handle entities to transfer objects conversions.
You can annotate the relations with fetchType=Lazy . Then invoke the getters to manually load the needed relations.
Another option is to eagerly load all relationships, but annotate the response with #JsonView and exclude the relations you don't need.

Parsing nested json received from an api to objects in spring boot

I am creating a spring boot application which receives some JSON data from a 3rd party api. The JSON data has so many nested objects.I want to map them as Java objects. Below is the code I wrote for getting the api response.
public ResponseEntity<MovieSearchResultsDto> getMovies(String searchText, String countryCode) {
logger.info("GetMovies Service started");
String url = prepareUrl(searchText,countryCode);
HttpHeaders header = new HttpHeaders();
prepareHeader(header);
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API for movie info");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
MovieSearchResultsDto.class);
}catch (Exception e) {
logger.error("Exception occured while calling the API "+ e);
if(responseEntity.getStatusCodeValue() != 200) {
}
}
logger.info("GetMovies Service Ended");
return responseEntity;
}
And the JSON response looks like
{
"results": [
{
"id": ******,
"picture": "url",
"name": "Titanic",
"locations": [
{
"icon": "url",
"display_name": "Amazon Instant Video",
"name": "AmazonInstantVideoIVAGB",
"id": "***",
"url": "url"
}
],
"provider": "iva",
"weight": 0,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "url",
"id": "tt0046435"
},
"tmdb": {
"url": "url",
"id": "id"
},
"wiki_data": {
"url": "url",
"id": "id"
},
"iva": null,
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
}
] }
What I have done is , I created a class MovieSearchResultsDto and include a list as its data member with getters and setters.
private List<MoviesDto> results = new ArrayList<>();
And created MoviesDto class as below
public class MoviesDto {
private String id;
private String name;
private String picture;
#JsonInclude(value = Include.NON_EMPTY)
private List<MovieLocation> locations = new ArrayList<MovieLocation>();
#JsonInclude(value = Include.NON_EMPTY)
private List<ExternalIds> external_ids = new ArrayList<ExternalIds>();
public MoviesDto() {
}
//getters and setters
}
class MovieLocation{
private String icon;
private String id;
private String display_name;
private String name;
private String url;
public MovieLocation() {
}
//getters and setters
}
class ExternalIds{
private IdAndUrl imdb;
private IdAndUrl tmdb;
private IdAndUrl wiki_data;
public ExternalIds() {
}
//getters and setters
}
class IdAndUrl{
private String url;
private String id;
public IdAndUrl() {
}
//getters and setters
}
But it shows error while parsing.
Exception occured while calling the API org.springframework.web.client.RestClientException: Error while extracting response for type [class com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1054] (through reference chain: com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto["results"]->java.util.ArrayList[0]->com.prebeesh1427.MovieNameServiceProvider.dto.MoviesDto["external_ids"])
I am a newbie to this area. Kindly help me not just only to resolve this issue but to understand the concept of these parsing techniques too.
Thanks in advance

How to parse belowJSON in springboot

How to parse the response JSON and get summary and action each time and form a separate object with that.
{
"issues": [
{
"fields":
{
"summary": "This is summary",
"action": "Start"
}
}, {
"fields":
{
"summary": "Second summary",
"action": "Stop"
}
}
]
}
You can create a Issues class and Fields class. Below is a snippet for reference,
public class Issues {
private List<Fields> fields;
// Getters and Setters
}
public class Fields {
private String summary;
private String action;
// Getters and Setters
}
You can map the response to the Issues object and go ahead iterating the fields List to fetch the summary and action out of it.

How to query nested objects from MongoDB using Spring Boot (REST) and TextQuery?

I am writing RESTful API to search MongoDB collection named 'global' by criteria using TextQuery. My problem is that I cannot access nested object fields when doing query.
For example this works:
GET localhost:8080/search?criteria=name:'name'
And this does not:
GET localhost:8080/search?criteria=other.othername:'Other Name'
I have MongoDB json structure (imported from JSON into 'global' collection as whole nested objects)
[{
"name": "Name",
"desc": "Desc",
"other" {
"othername": "Other Name",
}
},
{
"name": "Name",
"desc": "Desc",
"other" {
"othername": "Other Name",
}
}
]
And classes (with getters & setters & etc):
#Document(collection="global")
public class Global{
#TextIndexed
String name;
#TextIndexed
String desc;
Other other;
...
}
public class Other{
String othername;
...
}
My controller has method
#GetMapping("/search")
public Iterable<Global> getByCriteria(#RequestParam("criteria") String criteria) {
...
}
And I am trying to write text search with
public Iterable<Global> findByCriteria(String criteria) {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching(criteria);
TextQuery query = TextQuery.queryText(criteria);
return mongoTemplate.find(query, Global.class);
}
You need to add #TextIndexed to your other field.
public class Global{
#TextIndexed
String name;
#TextIndexed
String desc;
#TextIndexed
Other other;
...
}
Note: All nested object fields are searchable
or you may add #TextIndexed for each nested object field:
public class Other {
#TextIndexed
String othername;
...
}

I got null from entity with #Data when I use #Requestbody

I'm a junior web developer.
I don't understand why the authenticatedPgInfo return NULL
The follow is my simple test.
#RestController
public class InitController {
#PostMapping("/test")
public String test(#RequestBody AuthenticatedPgInfo authenticatedPgInfo) {
return "";
}
}
And
#Data
public class AuthenticatedPgInfo {
private int code;
private String message;
private List<AuthenticatedPg> result;
}
If I change #Data to #Getter and #Setter, authenticatedPgInfo return null.
But, When I remove #Data, #Getter...(Lombok Annotation) and generate getter and setter, then It works!!
I don't understand why Lombok doens't work in this situation..
Can somebody help?
Thanks in advance.
test.json
{
"result": [
{
"key": "rabbit",
"value": "3"
},
{
"key": "lion",
"value": "1"
}
],
"message": "success",
"code": 0
}
OMG... I Made a biiiiiiiiiig mistake.
I didn't check Enable annotation processingin IntelliJ
All code all works as expected. Thank you guys!
The #Data annotation implicitly includes #RequiredArgsConstructor, but not #NoArgsConstructor. Since Jackson needs a no argument constructor, then you would need to provide one. You can do that with Lombok by simply adding #NoArgsConstructor.
#Data
#NoArgsConstructor
public class AuthenticatedPgInfo {
private int code;
private String message;
private List<AuthenticatedPg> result;
}
Right now I reproduced the same example at my own - with #Data annotation only all works as expected.
To serialize json to the object, Jackson needs the default constructor of this object (i.e. without any parameters) or constructor with parameters and with #JsonCreator annotation.
By default Lombok creates the default constructor if any other does not exists. And Jackson use it. But maybe in your case something goes wrong, so you can create your DTO with #JsonCreator annotation, like this for example:
#Data
public class AuthenticatedPgInfo {
private int code;
private String message;
private List<AuthenticatedPg> result;
#JsonCreator
public AuthenticatedPgInfo(#JsonProperty("code") in code, #JsonProperty("mesage") String message, #JsonProperty("result") List<AuthenticatedPg> result) {
this.code = code;
this.message = message;
this.result = result;
}
}

Resources