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!
6 Answers
Answers 1
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") ); } }
Answers 2
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
Answers 3
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:
@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 !
Answers 4
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 ...
Answers 5
Go through the FLEXJSON library to smartly include/exclude nested class hierarchy while serializing Java objects.
Examples for flexjson.JSONSerializer
presented here
Answers 6
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 }
0 comments:
Post a Comment