Spring Boot Jackson - Selectively serialize embedded object properties based on their depth - json

I have edited the question to provide more clarity.
My problem statement is how can I customize the way Jackson serializes an Entity that's sent in my REST Response, such that my JSON doesn't become too large / unreadable?
Consider I have the following Entities (or resources / models, etc)
public class Department
{
private Long id;
private String name;
private Employee head;
/***
* Other fields, getters and setters
*/
}
And another one, like this
public class Employee {
private Long id;
private String name;
private Department department;
/***
* Other fields, getters and setters
*/
}
I have a REST API that gets an Employee by his/her ID, and here's the response when I call that
GET /api/employees/2
{
"id":"2",
"name":"John Doe",
"department":{
"id":3,
"name":"Product Support",
"head":{
"id":1,
"name":"The Chairman",
"department":{
"id":1,
"name":"Executive",
"head":null
}
}
}
}
The response, as you can see, is large (and can get really large depending on the data model). I want to stop the serialization from going deep into nested objects.
My preferrable JSON Response would be:
GET /api/employees/2
{
"id":"2",
"name":"John Doe",
"department":{
"id":3,
"name":"Product Support",
}
}
I already am aware that I can use the #JsonIgnore annotation on Department.head, like this:
public class Department
{
private Long id;
private String name;
#JsonIgnore
private Employee head;
/***
* Other fields, getters and setters
*/
}
But, this will skip serializing the head property permanently. For instance, see the below REST API call, post adding #JsonIgnore
GET /api/departments/3
{
"id":3,
"name":"Product Support",
}
// Here, I want the Full serialized form of the Department Object
So, as you can see, here's the question: I want the Department object to be serialized fully, when I call GET /api/departments/3. But, I want it to skip the head property when I call GET /api/employees/2. How do I achieve this in Jackson, when used in Spring Boot?
I also came across a library called Squiggly that uses a GraphQL like query language for the API responses, but I would like to know if I can do without having to learn a new library.
Thanks very much
Edit After Abbin Varghese's Answer
I changed my Employee model to the one below:
public class Employee {
private Long id;
private String name;
#JsonIgnoreProperties({"head"})
private Department department;
/***
* Other fields, getters and setters
*/
}
Upon serialization, this gave me the following JSON output:
{
"id":"2",
"name":"John Doe",
"department":{
"id":3,
"name":"Product Support",
}
}
The pro here, is that it solves my problem, without having to write a separate DTO class for my models.
The con however, is this: In this case, I'm trying to skip serialization only for the head property, so it makes sense to include this in the #JsonIgnoreProperties annotation. What if the list of properties to skip is longer than the list of properties to serialize? For example, if my Department as 15 properties, and I wish to serialize only 3 of them, then it becomes difficult to include the 12 properties to skip in the #JsonIgnoreProperties annotation.
Is there an annotation that does the opposite (Include only these fields, etc.)??

JsonProperty.Access.WRITE_ONLY is a good way to avoid elements.
For example..
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#ManyToOne
#JoinColumn(name = "MODIFIED_BY")
private User lastUpdatedBy;

Related

Is there a way to get "#JsonBackReference" only working when the element is in a collection, but not when standalone?

I am working with fasterxml, and I have two objects that have a relationship "one to many" or "many to one". For example the class Author and the class Book, one author has many books, and one book has one author. Can I tell fasterxml not to serialize the author of the book, only when the book is in it's author's books collection, but when the book is on itself, to serialize it.
Author class:
public class Author{
public int id;
public string name;
#JsonManagedReference
public Set<Book> books;
}
Book class:
public class Book{
public int id;
public string name;
#JsonBackReference
public Author author;
}
That setup works just fine if I want to get only the author, because the books are in place and theirs's author property isn't being serialized, but if I want to serialize only the book, it's author again isn't being serialized, because of the "#JsonBackReference" annotation. Is there any workaround in the said situation? Here are some more examples if you are not getting what I mean...
When I serialize an Autor:
{
id:3,
name: "Ivan Vazov"
books:[
{
id:5,
name: "Under the Yoke"
}
]
}
And that is what I want here.
When I serialize a Book:
{
id:5,
name: "Under the Yoke"
}
But i don't want this, I want this:
{
id:5,
name: "Under the Yoke",
author: {
id:3,
name: "Ivan Vazov"
}
}
Any thoughts on the matter would be great! Thanks.
If you want the references to be serialized from both sides(Book, Author) then jackson faces the issue with circular reference where it get's stuck in an endless loop of references when serializing one of those 2 objects.
The workaround was with #JsonManagedReference and #JsonBackReference where jackson ignored 1 side and serialized only the other side in order to avoid circular reference.
The solution to your problem (when you want to serialize both sides) is to create seperate DTO objects(AuthorDto, BookDto) and instead of returning from your controller a Author to be serialized you return an AuthorDto. Same with Book. Then circlular reference problem does not exist any more and both sides serialize the problematic references.
DTOs are the way to go in more complex scenarios, especially on the inbound side. For dynamic filtering of simpler use cases i wrote an addon for jackson to use antpath style filtering. Probably it helps you:
https://github.com/Antibrumm/jackson-antpathfilter
You have add fetch type in ManyToOne relationship side as follow.
To fetch all book entity then add fetch type Eager.
#ManyToOne(
fetch = FetchType.EAGER,
optional = false
)
To fetch only book entity then add fetch lazy.
#ManyToOne(
fetch = FetchType.LAZY,
optional = false
)

Spring Rest: Mapping a property of a bean as nested JSON

My Spring REST controller needs to map an object parameter that looks like this:
{
"batchId": 43091,
"domain": "XX",
"code": "XXX",
"effectiveDate": "2020-02-13",
"status": "Y",
"result": [{"ruleName":"name",...]}]
}
I'm having trouble coming up with the DTO to convert this data into. What I have so far looks like this:
#Data
#NoArgsConstructor
#EqualsAndHashCode
public class ValidationResult {
private String result;
private String status;
private String batchId;
private String domain;
private String code;
private String effectiveDate;
}
But result, which contains the embedded JSON, is always null. I don't care about that JSON being mapped, as I'm storing it as a JSON type in the database (Postgresql). But what Java type do I need to declare it to be to get the controller to convert it? I tried making it a javax.json.JsonObject, but that failed.
What we always do with those json inputs is to map those to specific classes. Which means, in your case, result could be a class which itself contains the given fields "ruleName" and their types. Then your Validaton Result containts a private Result result. If naming conventions are quite right the used mapper will be able to convert and map the response to the class and its properties.

REST: how to serialize a java object to JSON in a "shallow" way?

Suppose I have the following JPA entities:
#Entity
public class Inner {
#Id private Long id;
private String name;
// getters/setters
}
#Entity
public class Outer {
#Id private Long id;
private String name;
#ManyToOne private Inner inner;
// getters/setters
}
Both Spring and java EE have REST implementations with default serializers which will marshall the entities to/from JSON without further coding. But when converting Outer to JSON, both Spring and EE nest a full copy of Inner within it:
// Outer
{
"id": "1234",
"name": "MyOuterName",
"inner": {
"id": "4321",
"name": "MyInnerName"
}
}
This is correct behavior but problematic for my web services, since the object graphs can get deep/complex and can contain circular references. Is there any way to configure the supplied marshaller to marshall the POJOs/entities in a "shallow" way instead without having to create a custom JSON serializer for each one? One custom serializer that works on all entities would be fine. I'd ideally like something like this:
// Outer
{
"id": "1234",
"name": "MyOuterName",
"innerId": "4321"
}
I'd also like it to "unmarshall" the JSON back into the equivalent java object. Bonus kudos if the solution works with both Spring and java EE. Thanks!
After many problems I give reason to Cássio Mazzochi Molin saying that "the use of entities persistence in your REST API can not be a good idea"
I would do that the business layer transform persistence entities to DTO.
You can do this very easily with libraries like mapstruct
If you still want to continue with this bad practice you can use jackson and customize your jackson mapper
To unscramble complex object graphs using jaxb #XmlID and #XmlIDREF is made for.
public class JSONTestCase {
#XmlRootElement
public static final class Entity {
private String id;
private String someInfo;
private DetailEntity detail;
#XmlIDREF
private DetailEntity detailAgain;
public Entity(String id, String someInfo, DetailEntity detail) {
this.id = id;
this.someInfo = someInfo;
this.detail = detail;
this.detailAgain = detail;
}
// default constructor, getters, setters
}
public static final class DetailEntity {
#XmlID
private String id;
private String someDetailInfo;
// constructors, getters, setters
}
#Test
public void testMarshalling() throws JAXBException {
Entity e = new Entity( "42", "info", new DetailEntity("47","detailInfo") );
JAXBContext context = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[]{Entity.class}, null);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
m.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
m.marshal(e, System.out);
}
}
This will result in the following json-fragment
{
"detailAgain" : "47",
"detail" : {
"id" : "47",
"someDetailInfo" : "detailInfo"
},
"id" : "42",
"someInfo" : "info"
}
Unmarshalling of this json will ensure that detail and detailAgain are the same instances.
The two annotations are part of jaxb, so it will work in Spring as well as in java EE. Marshalling to json is not part of the standard, so i use moxy in the example.
Update
Explicitly using moxy is not neccessary in a JAX-RS Resource. The following snipped perfectly runs on a java-EE-7 container (glassfish 4.1.1) and results in the above json-fragment:
#Stateless
#Path("/entities")
public class EntityResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Entity getEntity() {
return new Entity( "42", "info", new DetailEntity("47","detailInfo") );
}
}
I had the same problem and ended up using jackson annotations on my Entities to control the serialization:
What you need is #JsonIdentityReference(alwaysAsId=true) to instruct the bean serializer that this reference should be only an ID. You can see an example on my repo:
https://github.com/sashokbg/company-rest-service/blob/master/src/main/java/bg/alexander/model/Order.java
#OneToMany(mappedBy="order", fetch=FetchType.EAGER)
#JsonIdentityReference(alwaysAsId=true) // otherwise first ref as POJO, others as id
private Set<OrderDetail> orderDetails;
If you want a full control of how your entities are represented as JSON, you can use JsonView to define which field is serialized related to your view.
#JsonView(Views.Public.class)
public int id;
#JsonView(Views.Public.class)
public String itemName;
#JsonView(Views.Internal.class)
public String ownerName;
http://www.baeldung.com/jackson-json-view-annotation
Cheers !
for this problem There are two solutions.
1-using jackson json view
2- Createing two mapping classe for innner entity. one of them includes custom fields and another one includes all fields ...
i think jackson json view is better solution ...
Go through the FLEXJSON library to smartly include/exclude nested class hierarchy while serializing Java objects.
Examples for flexjson.JSONSerializer presented here
You can detach the JPA entity before serialization, if you use lazyloading it's avoid to load sub objects.
Another way, but is depend of the JSON serializer API, you can use "transient" or specifics annotation.
Why does JPA have a #Transient annotation?
A bad way is to use tool like dozer to copy JPA object in another class with only the properties need for json (but it works... little overhead of memory, CPU and time...)
#Entity
public class Outer {
#Id private Long id;
private String name;
#ManyToOne private Inner inner;
//load manually inner.id
private final Long innerId;
// getters/setters
}

Spring Boot REST display id of parent only in a JSON response

Assume I have the following class:
public class ChildEntity {
...
#ManyToOne
private ParentEntity parent;
...
}
Now, I have a REST endpoint that retrieves a child entity object from the database, thus my JSON is the following:
{"id": "123", "name":"someName", "parent": { //parent fields here } ... }
I want to format my JSON responses in another way. I want parent display only the id from the database, instead of the whole object:
{"id": "123", "name":"someName", "parentId": "1" ... }
Basically returning entities directly from endpoints isn't a good idea. You make very tight coupling between DB model and responses. Instead, implement a POJO class that will be equivalent of the HTTP response you sent.
This POJO will have all ChildEntity fields and parentId only and will be constructed in HTTP layer.
Please, see the discussion in comments, basically such an object returned from web layer is not a DTO according to me.
I am annotating #JsonIgnore which ever field I do not want to be part of JSON response. Creating parallel POJO for each entity is costly affair.
#JsonIgnore
#NotNull
#Column(name="DELETED")
private boolean deleted = false;

Strange Mapping Behaviour Jackson JSON

I've got a strange mapping Issue with Jackson on Android.
I've got a "Content" Class which should be used by the Jackson Mapper.
It looks like this:
public class content {
private String header;
private String subheader;
private String bodytext;
#JsonProperty("singleimage")
private String image;
#JsonProperty("uid")
private String id;
#JsonProperty("link")
private String article;
#JsonProperty("CType")
private String cType;
// Eclipse auto generated getters & setters
...
}
The corresponding JSON Object looks like this:
{
"header": "xyz",
"subheader": "abc",
"bodytext": "abc",
"singleimage": "abc",
"images": "abc.jpg",
"teaser_elements": "",
"uid": "13",
"link": "xyz.htm",
"CType": "row_header"
}
Now when I use the Jackson Maper to create instances of Content from a provided JSON all fields of the content class get populated correctly - all except "cType".
I already tried to move the #JsonProperty("CType") annotation to the setCType Method but still no effect.
I don't get any Exceptions while mapping the class or anything else and as it seems to me that all mappings pretty much do the same (mapping to String) im kinda buffled why it doesn't work wit the "CType".
Any suggestions what the problem might be are highly appreciated.