Ignore inner object if all the properties are null during ObjectMapper deserialization - java-8

I have a below Product class
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product {
private String id;
private String status;
private Price price
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Price {
private String originalPrice;
private String newPrice;
}
After deserialization I'm getting the output json as below
{
"id" : 2113,
"status" : "New",
"price" : { },
}
But I'm expecting the output as below without price details as price has all the attributes as null
{
"id" : 2113,
"status" : "New"
}
I tried #JsonInclude(JsonInclude.Include.NON_EMPTY) at class level but its not working.
Any help is much appreciated.

This may be because your Price object is not null. Somewhere Price is initialized and included empty.
See example here
If this is not the case then, you can add code for your service/controller which returns Product.

Related

Moshi toJSON sorts alphabetically?

Can anyone tell why moshi toJSON giving alphabetical sorted JSON string
Model Class :
Class { String firstName; String emailID;}
Resulting JSON:
{ "emailID" : someMail, "firstName" : name }

Adding more information to the HATEOAS response in Spring Boot Data Rest

I have the following REST controller.
#RepositoryRestController
#RequestMapping(value = "/booksCustom")
public class BooksController extends ResourceSupport {
#Autowired
public BooksService booksService;
#Autowired
private PagedResourcesAssembler<Books> booksAssembler;
#RequestMapping("/search")
public HttpEntity<PagedResources<Resource<Books>>> search(#RequestParam(value = "q", required = false) String query, #PageableDefault(page = 0, size = 20) Pageable pageable) {
pageable = new PageRequest(0, 20);
Page<Books> booksResult = BooksService.findBookText(query, pageable);
return new ResponseEntity<PagedResources<Resource<Books>>>(BooksAssembler.toResource(BooksResult), HttpStatus.OK);
}
My Page<Books> BooksResult = BooksService.findBookText(query, pageable); is backed by SolrCrudRepository. When it is run BookResult has several fields in it, the content field and several other fields, one being highlighted. Unfortunately the only thing I get back from the REST response is the data in the content field and the metadata information in the HATEOAS response (e.g. page information, links, etc.). What would be the proper way of adding the highlighted field to the response? I'm assuming I would need to modify the ResponseEntity, but unsure of the proper way.
Edit:
Model:
#SolrDocument(solrCoreName = "Books_Core")
public class Books {
#Field
private String id;
#Field
private String filename;
#Field("full_text")
private String fullText;
//Getters and setters omitted
...
}
When a search and the SolrRepository is called (e.g. BooksService.findBookText(query, pageable);) I get back these objects.
However, in my REST response I only see the "content". I would like to be able to add the "highlighted" object to the REST response. It just appears that HATEOAS is only sending the information in the "content" object (see below for the object).
{
"_embedded" : {
"solrBooks" : [ {
"filename" : "ABookName",
"fullText" : "ABook Text"
} ]
},
"_links" : {
"first" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
},
"self" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook"
},
"next" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
},
"last" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
}
},
"page" : {
"size" : 1,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Just so you can get a full picture, this is the repository that is backing the BooksService. All the service does is call this SolrCrudRepository method.
public interface SolrBooksRepository extends SolrCrudRepository<Books, String> {
#Highlight(prefix = "<highlight>", postfix = "</highlight>", fragsize = 20, snipplets = 3)
HighlightPage<SolrTestDocuments> findBookText(#Param("fullText") String fullText, Pageable pageable);
}
Ok, here is how I did it:
I wrote mine HighlightPagedResources
public class HighlightPagedResources<R,T> extends PagedResources<R> {
private List<HighlightEntry<T>> phrases;
public HighlightPagedResources(Collection<R> content, PageMetadata metadata, List<HighlightEntry<T>> highlightPhrases, Link... links) {
super(content, metadata, links);
this.phrases = highlightPhrases;
}
#JsonProperty("highlighting")
public List<HighlightEntry<T>> getHighlightedPhrases() {
return phrases;
}
}
and HighlightPagedResourcesAssembler:
public class HighlightPagedResourcesAssembler<T> extends PagedResourcesAssembler<T> {
public HighlightPagedResourcesAssembler(HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) {
super(resolver, baseUri);
}
public <R extends ResourceSupport> HighlightPagedResources<R,T> toResource(HighlightPage<T> page, ResourceAssembler<T, R> assembler) {
final PagedResources<R> rs = super.toResource(page, assembler);
final Link[] links = new Link[rs.getLinks().size()];
return new HighlightPagedResources<R, T>(rs.getContent(), rs.getMetadata(), page.getHighlighted(), rs.getLinks().toArray(links));
}
}
I had to add to my spring RepositoryRestMvcConfiguration.java:
#Primary
#Bean
public HighlightPagedResourcesAssembler solrPagedResourcesAssembler() {
return new HighlightPagedResourcesAssembler<Object>(pageableResolver(), null);
}
In cotroller I had to change PagedResourcesAssembler for newly implemented one and also use new HighlightPagedResources in request method:
#Autowired
private HighlightPagedResourcesAssembler<Object> highlightPagedResourcesAssembler;
#RequestMapping(value = "/conversations/search", method = POST)
public HighlightPagedResources<PersistentEntityResource, Object> findAll(
#RequestBody ConversationSearch search,
#SortDefault(sort = FIELD_LATEST_SEGMENT_START_DATE_TIME, direction = DESC) Pageable pageable,
PersistentEntityResourceAssembler assembler) {
HighlightPage page = conversationRepository.findByConversationSearch(search, pageable);
return highlightPagedResourcesAssembler.toResource(page, assembler);
}
RESULT:
{
"_embedded": {
"conversations": [
..our stuff..
]
},
"_links": {
...as you know them...
},
"page": {
"size": 1,
"totalElements": 25,
"totalPages": 25,
"number": 0
},
"highlighting": [
{
"entity": {
"conversationId": "a2127d01-747e-4312-b230-01c63dacac5a",
...
},
"highlights": [
{
"field": {
"name": "textBody"
},
"snipplets": [
"Additional XXX License for YYY Servers DCL-2016-PO0422 \n  \n<em>hi</em> bodgan \n  \nwe urgently need the",
"Additional XXX License for YYY Servers DCL-2016-PO0422\n \n<em>hi</em> bodgan\n \nwe urgently need the permanent"
]
}
]
}
]
}
I was using Page<Books> instead of HighlightPage to create the response page. Page obviously doesn't contain content which was causing the highlighted portion to be truncated. I ended up creating a new page based off of HighlightPage and returning that as my result instead of Page.
#RepositoryRestController
#RequestMapping(value = "/booksCustom")
public class BooksController extends ResourceSupport {
#Autowired
public BooksService booksService;
#Autowired
private PagedResourcesAssembler<Books> booksAssembler;
#RequestMapping("/search")
public HttpEntity<PagedResources<Resource<HighlightPage>>> search(#RequestParam(value = "q", required = false) String query, #PageableDefault(page = 0, size = 20) Pageable pageable) {
HighlightPage solrBookResult = booksService.findBookText(query, pageable);
Page<Books> highlightedPages = new PageImpl(solrBookResult.getHighlighted(), pageable, solrBookResult.getTotalElements());
return new ResponseEntity<PagedResources<Resource<HighlightPage>>>(booksAssembler.toResource(highlightedPages), HttpStatus.OK);
}
Probably a better way of doing this, but I couldn't find anything that would do what I wanted it to do without having a change a ton of code. Hope this helps!

Spring rest controller giving unsupported content type

Hello all here is what i have:
StockController.java
#RestController
public class StockController {
#Autowired
private StockRepository repository;
#RequestMapping(value = "stockmanagement/stock")
public ResponseEntity<?> addStock(#RequestBody String stock
) {
System.out.println(stock);
return new ResponseEntity<>(HttpStatus.OK);
}
when I make a request like so using chrome advanced rest extension :
Raw Headers
Content-Type: application/json
Raw Payload
{"stock": {"productId": 2, "expiryAndQuantity" : {}, "id": 0}}
It works fine in that out comes a string of json
However when i try to replace String stock with Stock stock where stock looks like this:
public class Stock {
#Id
private String id;
private String productId;
private Map<LocalDateTime, Integer> expiryAndQuantity;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public Map<LocalDateTime, Integer> getExpiryAndQuantity() {
return expiryAndQuantity;
}
public void setExpiryAndQuantity(Map<LocalDateTime, Integer> expiryAndQuantity) {
this.expiryAndQuantity = expiryAndQuantity;
}
#Override
public String toString() {
return String.format(
""
);
}
}
I get an error where by the following is fed back to me:
"status": 415
"error": "Unsupported Media Type"
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException"
"message": "Content type 'application/json;charset=UTF-8' not supported"
"path": "/stockmanagement/stock"
My question is; how do i create a request which maps to my Stock object.
You can try with #JsonRootName annotation, by default Spring serialize using no root name value. like this:
{"productId": 2, "expiryAndQuantity" : {}, "id": 0}
But if you want that your serialization has a rootname you need to use #JsonRootName annotation.
#JsonRootName(value = "Stock")
And it'll produce something like this
{"Stock": {"productId": 2, "expiryAndQuantity" : {}, "id": 0}}
You can see more here
http://www.baeldung.com/jackson-annotations
instead of accepting a String Accept a Stock object.and accept it from a post request than having a get request
#RequestMapping(value = "stockmanagement/stock",method=RequestMethod.POST)
public ResponseEntity<?> addStock(#RequestBody Stock stock){
}
and your request should be sent like this
{
"productId": 2
,"expiryAndQuantity" : null
,"id": 0
}
all parameter names should be equal to the objects filed names,since spring has jackson binders on class path and object will be created inside the controller method. if you are planning on passing different parameters from the post request you can use
#JsonProperty("pid")
private String productId;
on the field name.

Spring data elasticSearch returns null with findOne

I'm testing Spring data with elasticSearch. The ES server is running on a remote server in tha same room.
I have one index created a day, under an alias. I'm trying to find a simple tweet. But when I try a findOne(), it doesn't seem to work because it returns always null.
Also, findAll(ids) doesn't work because I'm using the alias, but I can't find in the documentation how to handle this.
What do I want to achieve ?
For the moment, simply retrieve a tweet with a given id_str.
The count method works, the findOne doesn't
Here are my questions
What should I do to make findOne() to work ?
Which way should I use to search on multiple indexes in this alias ?
Here is how the datas looks like in ES
{
"id_str" : "135131315100051",
"..." : "...",
"user" : {
"id_str" : "15843643228"
"..." : "..."
}
}
My model
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
#Document(indexName = "alias", type = "tweets")
public class Tweet
{
#Id
#Field(type = FieldType.String)
private String idStr;
public String getIdStr()
{
return idStr;
}
public void setIdStr(final String idStr)
{
this.idStr = idStr;
}
#Override
public String toString()
{
return "{ id_str : " + idStr + " }";
}
}
Alias is alias, and indexes are alias_dd-mm-yyyy
My repository
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.thales.communications.osintlab.bigdata.webservices.models.Tweet;
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
Tweet findByIdStr(String idStr);
}
My test
#Test
public void shouldReturnATweet()
{
//lets try to search same record in elasticsearch
final Tweet tweet1 = tweetRepository.findOne("593768150975512576");
//final Tweet tweet = tweetRepository.findByIdStr("593897683661824000");
System.out.println("Count is " + tweet1);
//System.out.println("Count is " + tweetRepository.count());
// System.out.println(tweet.toString());
}
Of course, the tweet with the tested Id exists :). And the count() is working fine.
Thanks for your help
EDIT
Here is a sample application of what I have : https://github.com/ogdabou/es-stackoverflow-sample
It seems that spring-data-elasticsearch is look for the field "_id" and not the field "id_str". Maybe because of method parsing (look there). I'm looking for a way to bind my json "id_str" attribute to my idStr java model.
What was the real issue
We set the _id field of our tweet in Elasticsearch with the id field given by twitter. But it saves it in another format ( eg 132 becomes 1.32E2)
When I'm going a findOne() it is searching for a match with the Elasticsearch _id field and not the id_str I needed.
Solution
There, you have 2 commits, the first is the issue, the second the solution.
New repository
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
#Query("{\"bool\" : {\"must\" : {\"term\" : {\"id_str\" : \"?0\"}}}}")
Tweet findByIdStr(String idStr);
}
The model
#Document(indexName = "my_index_01", type = "tweets")
public class Tweet
{
// Elasticsearch object internal id. Look at field "_id"
#Id
private String id;
// Twitter internal id, saved under the "id_str" field
#Field(type = FieldType.String)
private String id_str;
#Field(type = FieldType.String)
private String text;
public String getId_str()
{
return id_str;
}
public void setId_str(final String id_str)
{
this.id_str = id_str;
}
public String getText()
{
return text;
}
public void setText(final String text)
{
this.text = text;
}
public String getId()
{
return id;
}
public void setId(final String id)
{
this.id = id;
}
#Override
public String toString()
{
return "{ _id : " + id + ", id_str : " + id_str + ", text : " + text + " }";
}
}

Spring: one JPA model, many JSON respresentations

I'm writing a RESTful web service using Spring/JPA. There's a JPA model which is exposed through the web service. The 'Course' model is quite spacious - it actually is composed of several sets of data: general information, pricing details and some caches.
The issue I encounter is the inability to issue different JSON representations using the same JPA model.
The in first case I only need to return general_info set of data for courses:
GET /api/courses/general_info
in the second case I would like to return pricing set of data only:
GET /api/courses/pricing
I see the following ways to solve this, not in particular order:
To create CourseGeneralInfo and CoursePricing JPA models using
the origin database table as a source. CourseGeneralInfo model
would have its own set of fields and CoursePricing would have its
own ones. This way I would have the JSON I need.
To refactor the stuff out of the Course model/table to have
GeneralInfo and PricingDetails to be separate JPA entities. Ok, this sounds like the best one (imo) though the database is legacy and it is not something I can change easily...
Leverage some sort of DTO and Spring Mappers to convert the JPA model to representation needed in any particular case.
What approach would you recommend?
I was just reading about some really nifty features in Spring 4.1, which allow you to use different views via annotations.
from: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
public class View {
interface Summary {}
}
public class User {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private String firstname;
#JsonView(View.Summary.class)
private String lastname;
private String email;
private String address;
private String postalCode;
private String city;
private String country;
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
private List<User> recipients;
private String body;
}
Thanks to Spring MVC #JsonView support, it is possible to choose, on a per handler method basis, which field should be serialized:
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.Summary.class)
#RequestMapping("/")
public List<Message> getAllMessages() {
return messageService.getAll();
}
#RequestMapping("/{id}")
public Message getMessage(#PathVariable Long id) {
return messageService.get(id);
}
}
In this example, if all messages are retrieved, only the most important fields are serialized thanks to the getAllMessages() method annotated with #JsonView(View.Summary.class):
[ {
"id" : 1,
"created" : "2014-11-14",
"title" : "Info",
"author" : {
"id" : 1,
"firstname" : "Brian",
"lastname" : "Clozel"
}
}, {
"id" : 2,
"created" : "2014-11-14",
"title" : "Warning",
"author" : {
"id" : 2,
"firstname" : "Stéphane",
"lastname" : "Nicoll"
}
}, {
"id" : 3,
"created" : "2014-11-14",
"title" : "Alert",
"author" : {
"id" : 3,
"firstname" : "Rossen",
"lastname" : "Stoyanchev"
}
} ]
In Spring MVC default configuration, MapperFeature.DEFAULT_VIEW_INCLUSION is set to false. That means that when enabling a JSON View, non annotated fields or properties like body or recipients are not serialized.
When a specific Message is retrieved using the getMessage() handler method (no JSON View specified), all fields are serialized as expected:
{
"id" : 1,
"created" : "2014-11-14",
"title" : "Info",
"body" : "This is an information message",
"author" : {
"id" : 1,
"firstname" : "Brian",
"lastname" : "Clozel",
"email" : "bclozel#pivotal.io",
"address" : "1 Jaures street",
"postalCode" : "69003",
"city" : "Lyon",
"country" : "France"
},
"recipients" : [ {
"id" : 2,
"firstname" : "Stéphane",
"lastname" : "Nicoll",
"email" : "snicoll#pivotal.io",
"address" : "42 Obama street",
"postalCode" : "1000",
"city" : "Brussel",
"country" : "Belgium"
}, {
"id" : 3,
"firstname" : "Rossen",
"lastname" : "Stoyanchev",
"email" : "rstoyanchev#pivotal.io",
"address" : "3 Warren street",
"postalCode" : "10011",
"city" : "New York",
"country" : "USA"
} ]
}
Only one class or interface can be specified with the #JsonView annotation, but you can use inheritance to represent JSON View hierarchies (if a field is part of a JSON View, it will be also part of parent view). For example, this handler method will serialize fields annotated with #JsonView(View.Summary.class) and #JsonView(View.SummaryWithRecipients.class):
public class View {
interface Summary {}
interface SummaryWithRecipients extends Summary {}
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
#JsonView(View.SummaryWithRecipients.class)
private List<User> recipients;
private String body;
}
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.SummaryWithRecipients.class)
#RequestMapping("/with-recipients")
public List<Message> getAllMessagesWithRecipients() {
return messageService.getAll();
}
}
In Spring Data REST 2.1 there is a new mechanism for this purpose - Projections (It's now part of spring-data-commons).
You'll need to define interface, containing exactly exposed fields:
#Projection(name = "summary", types = Course.class)
interface CourseGeneralInfo {
GeneralInfo getInfo();
}
After that Spring will be able to find it automagically in your source, and you could make requests to your existing endpoints, like this:
GET /api/courses?projection=general_info
Based on
https://spring.io/blog/2014/05/21/what-s-new-in-spring-data-dijkstra
Spring sample project with projections:
https://github.com/spring-projects/spring-data-examples/tree/master/rest/projections

Resources