Laokoon
Laokoon

Reputation: 1301

Circular Json serializing

i have a javaEE application with two entities which gets persistet in a database. both entities have a bi-directional association.

First Entity:

@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Child.class)
public class Child implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(fetch= FetchType.LAZY)
    private Father father;
}

Second Entity

@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=Father.class)
public class Father implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(fetch= FetchType.LAZY)
    private Child child;
}

Both are exposed via ressources like:

@Path("father")
@Stateless
public class FatherResource{

    @Context
    private UriInfo uri;

    @Inject FatherDao fatherDao;

    public FatherResource() {
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Father> getFathers() {
        //TODO return proper representation object
        return fatherDoa.getFathers();
    }
}

There is also a DAO which just gets the Father-Object from the database.

The problem is now, that i get a circular json structure. so something like:

{ "id":"1", "child": {"id":"1", "father": {"id":"1", "child":{"id":"1", [...] 

I just want to see the child once.

I tried to use:

@JsonIgnoreProperties({"child"})  //above the class
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")  //above the class
@JsonIgnore    //above the property

what i want to get is:

{ "id":"1", "child": {"id":"1"}}

I'm using

JDK 7
JaxRS
Jackson
Hibernate/Eclipselink
Glassfish 4.0

another test which doesn't work:

@Entity
@JsonSerialize
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="id", scope=Object.class)
public class Father implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(fetch= FetchType.LAZY)
    private Child child;
}

ApplicationConfig.java:

public class ApplicationConfig extends Application {

@Override
public Set<Class<?>> getClasses() {
    Set<Class<?>> resources = new java.util.HashSet<Class<?>>();
    // following code can be used to customize Jersey 2.0 JSON provider:

    try {
        Class jsonProvider = Class.forName("org.glassfish.jersey.jackson.JacksonFeature");

    } //...
//..

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="annotated">
</beans>

Upvotes: 0

Views: 1468

Answers (2)

user3227576
user3227576

Reputation: 574

Here is an example, which works and addresses your problem. Jackson is able to serialize your recursions. Have a look at this:

3 POJOs (A, B, C) and a test. Tested with jackson 2.2.3.

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class RecClassA {

    private RecClassA mySelf;
    private RecClassB b;

    private String value = "AV";

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public RecClassA getMySelf() {
        return mySelf;
    }

    public void setMySelf(RecClassA mySelf) {
        this.mySelf = mySelf;
    }

    public RecClassB getB() {
        return b;
    }

    public void setB(RecClassB b) {
        this.b = b;
    }

}

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class RecClassB {

    private String value = "BV";
    private RecClassC c;
    private RecClassA a;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public RecClassC getC() {
        return c;
    }

    public void setC(RecClassC c) {
        this.c = c;
    }

    public RecClassA getA() {
        return a;
    }

    public void setA(RecClassA a) {
        this.a = a;
    }

}

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class RecClassC {

    private String value = "CV";
    private RecClassA a;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public RecClassA getA() {
        return a;
    }

    public void setA(RecClassA a) {
        this.a = a;
    }

}

import org.testng.annotations.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ObjectMapperRecursionTest {

    private ObjectMapper om = new ObjectMapper();

    @Test
    public void simpleWriteTest() throws JsonProcessingException{
        RecClassA a = new RecClassA();

        String result = om.writeValueAsString(a);
        System.out.println("simpleWriteTest: " + result);
    }

    @Test
    public void simpleRecursionWriteTest() throws JsonProcessingException{
        RecClassA a = new RecClassA();
        a.setMySelf(a);
        String result = om.writeValueAsString(a);
        System.out.println("simpleRecursionWriteTest: " + result);
    }

    @Test
    public void abbaRecursionWriteTest() throws JsonProcessingException{
        RecClassA a = new RecClassA();
        RecClassB b = new RecClassB();

        a.setB(b);
        b.setA(a);

        String result = om.writeValueAsString(a);
        System.out.println("abbaRecursionWriteTest: " + result);
    }

    @Test
    public void abcaRecursionWriteTest() throws JsonProcessingException{
        RecClassA a = new RecClassA();
        RecClassB b = new RecClassB();
        RecClassC c = new RecClassC();

        a.setB(b);
        b.setC(c);
        c.setA(a);

        String result = om.writeValueAsString(c);
        System.out.println("abcaRecursionWriteTest: " + result);
    }

}

Here the result of the test:

abbaRecursionWriteTest: {"@id":1,"mySelf":null,"b":{"@id":2,"value":"BV","c":null,"a":1},"value":"AV"}
abcaRecursionWriteTest: {"@id":1,"value":"CV","a":{"@id":2,"mySelf":null,"b":{"@id":3,"value":"BV","c":1,"a":null},"value":"AV"}}
simpleRecursionWriteTest: {"@id":1,"mySelf":1,"b":null,"value":"AV"}
simpleWriteTest: {"@id":1,"mySelf":null,"b":null,"value":"AV"}

Upvotes: 1

marcus
marcus

Reputation: 5199

You could write a loop that sets father to null before returning the list. That way you won't give a circular data structure to the JSON serializer.

This solution may not look very clean, but it works and since nobody else has answered this question yet I thought it was worth mentioning.

Upvotes: 1

Related Questions