11

I'm using jackson 1.9.2 with Hibernate/Spring MVC through MappingJacksonHttpMessageConverter.

Jackson can not serialize bidirectional one-to-many relationship and makes an infinite loop.

The classes I'm using are :

  • Conversation which has a Set of SMS instances.

  • Each SMS instance has a Set of PhoneNumbers

  • Each PhoneNumber has a parent contact (this is the bidirectional many-to-one relationship)

What I am trying to do is to serialize a conversation.

If I don't use @JsonManagedReference and @JsonBackReference then jackson will crashe due to an infinite loop. But when I use them, the Contact doesn't get serialized into the PhoneNumber.

Class Contact {
  @JsonManagedReference
  List<PhoneNumber> phoneNumber ;
}
Class PhoneNumber {
  @JsonBackReference 
  Contact contact;
}

The output is :

{    <--------------------- Conversation
    "id": 51,
    "smsSet": [
      {
        "id": 53,
        "origin": 0123465,
        "destination": "06533844XY",
        "message": "Hello world!",
        "phoneNumbers": [
          {
            "id": 64,
            "num": "06533844XY",
            "creationDate": 1333992533000,
          }
        ],
      }
    ],
    "creationDate": 1333992534000
  }

instead of

{    <---------- conversation
    "id": 51,
    "smsSet": [
      {
        "id": 53,
        "origin": 0123465,
        "destination": "06533844XY",
        "message": "Hello world!",
        "phoneNumbers": [
          {
            "id": 64,
            "num": "06533844XY",
            "creationDate": 1333992533000,
            "contact":  <--------------------- Missing part
             {
                "id": 12,
                "name": "Samuel Jackson",
                "primaryNumber": "06533844XY"
             }
          }
        ],
      }
    ],
    "creationDate": 1333992534000
  }
redochka
  • 12,345
  • 14
  • 66
  • 79

4 Answers4

7

I recently encountered a similar problem: Jackson - serialization of entities with birectional relationships (avoiding cycles)

So the solution is to upgrade to Jackson 2.0, and add to classes the following annotation:

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

Then the problem is that Spring doesn't work with Jackson 2.0. This has been solved in the following way:

<bean id="jacksonMessageConverter"
          class="own.implementation.of.MappingJacksonHttpMessageConverter"/>

<bean class="org.springframework.web.servlet.mvc
             .annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jacksonMessageConverter"/>
            </list>
        </property>
        <property name="requireSession" value="false"/>
    </bean>

And the own.implementation.of.MappingJacksonHttpMessageConverter is based on this:

http://www.jarvana.com/jarvana/view/org/springframework/spring-web/3.0.0.RELEASE/spring-web-3.0.0.RELEASE-sources.jar!/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java?format=ok

But use ObjectMapper and other Jackson classes from Jackson 2.0 instead of Jackson 1.*

Eugene Retunsky
  • 13,009
  • 4
  • 52
  • 55
  • 1
    Thanks, Just tried this, and it seems to be working with only the first Conversation in the returned Conversations List. Any idea? Where should I put the JsonIdentityInfo? – redochka Apr 17 '12 at 12:22
  • You need to put JsonIdentityInfo for both classes (Contact and PhoneNumber) and remove JsonBack/ManagedReference annotations (as far as I understood from your question). – Eugene Retunsky Apr 17 '12 at 14:14
  • When I remove the JsonBack/ManagedReference annotations I get the following error: com.fasterxml.jackson.databind.JsonMappingException: Class com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator has no default (no arg) constructor (through reference chain: java.util.ArrayList[0]->com.model.Conversation["smsSet"]->java.util.LinkedHashSet[0]->com.model.SmsBean["phoneNumbers"]->org.hibernate.collection.PersistentSet[0]) – redochka Apr 21 '12 at 20:15
  • I have the same problem as @redochka. Seems ok with the solution from above but is working well in general, but if in a list an object is present twice in the list, on second attempt is present only the object id instead of the object itself – Aditzu Oct 04 '16 at 18:16
2

As a first solution, I kept these annotations and created an other class: ContactWithouPhoneNumber and added it as a field to the PhoneNumber class.

Now, before rendering, I copy all fields except PhoneNumber, from contact to contactWithoutPhoneNumber. The output JSON contains every thing I need.

This is the DTO design pattern.

redochka
  • 12,345
  • 14
  • 66
  • 79
0

One thing wrong in your class def is use of untyped List; you should rather have:

List<PhoneNumber> phoneNumber ;

since otherwise Jackson can not know what the type is when deserializing; and even on serializing it could cause problems (as base type is not known for sure). So I would first fix this issue.

But additionally your dependency may be wrong way around: @JsonManagedReference MUST always be the first thing to serialize. If this is not the case, you can not use these annotations. Without seeing objects you are trying to serialize it is hard to know for sure (POJO definition and JSON seem slightly off).

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • 1
    I was using the Generic version, stackoverflow editor has swallowed the <. I think that what's happening is that the @JsonBackReference is being serialized first. – redochka Apr 11 '12 at 21:31
  • Ah ok. Yes, that seems more likely; error would be different otherwise. – StaxMan Apr 11 '12 at 23:11
0

You can add @JsonIgnore to fields and the mapper will skip over them during serialization. It is similar in function to @Transient.

It is in the jackson-core-asl.

eric
  • 31
  • 1
  • 6