14

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!

Alvin Thompson
  • 5,388
  • 3
  • 26
  • 39
  • 1
    That's why using persistence entities in your REST API may not be a good idea. Have a look at this [answer](http://stackoverflow.com/a/36175349/1426227). Is using tailored DTOs an option? – cassiomolin Aug 10 '16 at 14:33
  • 1
    @CássioMazzochiMolin: I like the approach, but the project is now pretty mature. It's not practical at this point to switch all existing services (and their consumers) to use DTOs instead of the entities, and I don't want to "mix and match". – Alvin Thompson Aug 10 '16 at 17:27

6 Answers6

2

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") );
    }
}
frifle
  • 810
  • 5
  • 24
  • This approach would require me to manually create a jaxb marshaller in every endpoint and use that instead of the marshaller built into Spring/EE, which is a lot of extra boilerplate. As mentioned, both Java EE and Spring automatically marshal/unmarshall objects in the endpoints with no extra code so I'd like to leverage that. The whole point of the question is to avoid that kind of boilerplate--otherwise I'd just create a custom Jackson marshaller for each object. – Alvin Thompson Aug 11 '16 at 14:36
  • In other words, I don't want to clutter my endpoints with additional code--especially not with code that isn't directly related to the task at hand. I also don't want to have to create/register custom marshallers for every class because keeping them in synch with the objects as they evolve would be a headache. I just want to alter the behavior of the supplied marshaller so that it doesn't embed nested objects, but instead just gives their id. – Alvin Thompson Aug 11 '16 at 14:47
  • Using moxy explicitly is an artefact of this simple testcase. Jax-RS is capable of marshalling to json per default. You dont have to create a jaxb marshaller manually. Just add the annotations `@XmlID` and `@XmlIDREF` to your entities. – frifle Aug 11 '16 at 15:30
  • I tried that and it doesn't seem to work (that is, it continues to nest the JSON for the inner object instead of just the ID). I added `@XmlID` to `Inner.id` and `@ XmlIDREF` to `Outer.inner`. – Alvin Thompson Aug 11 '16 at 17:39
  • @Alvin I dont know why it doesnt work for you. Ive added an example JAX-RS Resource in the above text which runs fine on a standard glassfish 4.1.1. – frifle Aug 12 '16 at 07:34
  • I only tested with Spring; I'll test with Java EE and see if it works there. – Alvin Thompson Aug 12 '16 at 14:41
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

  • As I mentioned, I'd rather not have to create a custom serializer for every entity. Since we have a lot of entities and they change often, it would be a maintenance headache. – Alvin Thompson Aug 10 '16 at 17:23
  • Using the library that I mentioned that is no problem. The mapper are created by annotations. The only thing you should do is rewrite the annotations. Still, what use to marshall library? can you use Jackson ? – Jorge Jiménez Barra Aug 10 '16 at 17:34
  • I was referring to your suggestion for customizing the jackson mapper. DTOs are a good approach, but it's too late at this point to change the code to use them, so Mapstruct is a non-starter. – Alvin Thompson Aug 11 '16 at 14:04
1

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 !

sashok_bg
  • 2,436
  • 1
  • 22
  • 33
0

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 ...

yousef
  • 92
  • 1
  • 10
0

Go through the FLEXJSON library to smartly include/exclude nested class hierarchy while serializing Java objects.

Examples for flexjson.JSONSerializer presented here

Akash Mishra
  • 682
  • 1
  • 5
  • 13
  • as i mentioned in the question, i don't want to have to create a custom serializer for every object. – Alvin Thompson Aug 18 '16 at 11:47
  • I definitely think you can create a custom serializer at one place, making it to extend `JSONSerializer` putting a wildcard expression and override the `serialize()` method. Don't specify usage of `include()` method. This will help ensure that `serializer` doesn't include nested classes anytime. You can then try calling this to serialize different objects.Try incorporating this, ignore if it still doesn't solve your purpose. – Akash Mishra Aug 18 '16 at 14:55
-1

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
}
Community
  • 1
  • 1
Mr_Thorynque
  • 1,749
  • 1
  • 20
  • 31
  • I tried something similar, but if you detach the entity before serialization, it won't serialize and instead just throw an "entity detached" exception when the serializer tries to access `inner`. I also thought of using `@JsonIgnore` on `inner` and adding a separate `innerId` field, similar to what you suggest, but that would require a lot of boilerplate code for every class to keep them in sync. – Alvin Thompson Jul 28 '16 at 13:43
  • ok first way does not work. sorry. EclipseLink gives its solution http://www.eclipse.org/eclipselink/documentation/2.6/solutions/restful_jpa002.htm Using mappedBy to avoid circular references and using reference (_link) to provide url of nested objects – Mr_Thorynque Jul 28 '16 at 14:39
  • unfortunately, `mappedBy` allows you to specify where the relationship is defined in JPA but doesn't affect the actual objects. – Alvin Thompson Jul 28 '16 at 15:07
  • Maybe creating a HATEOAS Rest service resolve your problem. https://en.wikipedia.org/wiki/HATEOAS. I don't knwo what can do the client with the data "innerId": "4321" so you can also use transient on ManyToOne and not add "innerId" – Mr_Thorynque Jul 29 '16 at 06:24