jackson-databind: Deserialize Json Array that contin 2 objects with the same ID - JsonMappingException: Already had POJO for id

How can I deserialize an Array with two objects that contains the same SubObject with the same Id?

For example this is the JSON of the array

[
    {
        "@id": 98,
        "age": 29,
        "name": "mkyong",
        "relatedPackage": {"@id":99, "receivedOn":1374012807237 }
    },
    {
        "@id": 101,
        "age": 25,
        "name": "luis",
        "relatedPackage": {"@id":99, "receivedOn":1374012807237 }
    }
]

As you can see both objects has the same related package. I want that the deserializer only parse the first relatedPackage and use it for the secon relatedPackage.

My Pojos are the next:

User Class:

package com.mkyong.core;

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

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

    public int age;
    public String name;
    public RelatedPackage relatedPackage;

    //getter and setter methods

    @Override
    public String toString() {
        return "User [age=" + age + ", name=" + name + ", " +
                "messages=" + relatedPackage + "]";
    }
}

RelatedPackage Class:

package com.mkyong.core;

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

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class,property="@id")
public class RelatedPackage {
    public long receivedOn;

    public String toString() {
        return "{ receivedOn: " + receivedOn + " }";
    }
}

And finaly the main Object wich realize the deserializtion:

package com.mkyong.core;

import java.io.File;
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;


public class JacksonExample {
    public static void main(String[] args) {

    ObjectMapper mapper = new ObjectMapper();

    try {

        // read from file, convert it to user class
        File file = new File("/home/lvargas/user.json");
        User[] userList = mapper.readValue(file, User[].class);

        // display to console
        System.out.println(userList);

    } catch (JsonGenerationException e) {

        e.printStackTrace();

    } catch (JsonMappingException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }

  }

}

When both relatedPackage has the same Id it sends me the next error message

com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.Integer) [99] (through reference chain: com.mkyong.core.User["relatedPackage"]->com.mkyong.core.RelatedPackage["@id"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:197)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1332)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:252)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:115)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:449)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:107)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:250)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:115)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:151)
    at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:18)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1988)
    at com.mkyong.core.JacksonExample.main(JacksonExample.java:20)
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.Integer) [99]
    at com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.bindItem(ReadableObjectId.java:27)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeSetAndReturn(ObjectIdValueProperty.java:91)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeAndSet(ObjectIdValueProperty.java:80)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:250)
    ... 10 more

I understand that it is trying to create a second object with the same objectId.

How can I solve that problem?

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Comments: 35 (14 by maintainers)

Most upvoted comments

I’m not sure if this remains an open issue or not, but I figured it might help to have a minimal project with an obvious use-case. So I’ve cut down a project I’m working on here to be as small as I can make it.

I want to make a library API. Where users can add Books, which have a ManyToMany relationship with Authors. A book may have multiple Authors, and an Author may write multiple books, so if we serialize a book to JSON and include all of the information about all of its Authors, and then we serialize all of those, and then they all have information about their Books, so then we serialize all those…

An Author is very simple, the name of the author is the ID. That’s it. A Book is an ID, and a title. They have a ManyToMany relationship between them.

So we could POST a book like this, which would make a book, and map it to an author, which we may already have in the database, or which we might be making from her scratch:

POST /book
{
    "title": "Harry Potter and the Philosopher's Stone",
    "authors": [
        {
            "name": "JK Rowling"
        }
    ]
}

Works great! But then we would need to make one HTTP request per book, which could incur a performance constraint. So we want to have another endpoint for adding lots of them:

POST /books
[
    {
        "title": "Harry Potter and the Philosopher's Stone",
        "authors": [
            {
                "name": "JK Rowling"
            }
        ]
    },
    {
        "title": "Harry Potter and the Chamber of Secrets",
        "authors": [
            {
                "name": "JK Rowling"
            }
        ]
    }
]

Unfortunately this will be a problem, because Jackson will deserialize the two “JK Rowling” separately, and bomb out, and even though they are the exact same object, Jackson doesn’t understand how to map them together. My solution was to have the two objects simply resolve to be the same thing. The exact same object reference.

In the Entity:

    @JsonIdentityInfo(
            generator=ObjectIdGenerators.PropertyGenerator.class, 
            property="name", 
            scope = Author.class, 
            resolver = DedupingObjectIdResolver.class
    )

The ObjectIdResolver class:

package hello;

import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;

import java.util.HashMap;

public class DedupingObjectIdResolver extends SimpleObjectIdResolver {
    @Override
    public void bindItem(IdKey id, Object ob) {
        if (_items == null) {
            _items = new HashMap<>();
        }
        _items.put(id, ob);
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return new DedupingObjectIdResolver();
    }
}

I’ll upload the final project code in a sec here.

You can send your QUESTIONS to mailings lists. Issue trackers are for reporting problems and requesting new features.

Guys this is a bug. It is not a question! I can confirm that this is broken from Jackson 2.4 that added ObjectIdResolver. As an example the following deserialization will fail if Category/Id is an @XmlID. Foo { “Item”: [{ “Id”: 1, “Name”: “Item 1”, “Category”: { “Id”: 1} }, { “Id”: 2, “Name”: “Item 2”, “Category”: {“Id”: 1}} ]

These is a bug and should be fixed!

I’m not sure if this remains an open issue or not, but I figured it might help to have a minimal project with an obvious use-case. So I’ve cut down a project I’m working on here to be as small as I can make it.

I want to make a library API. Where users can add Books, which have a ManyToMany relationship with Authors. A book may have multiple Authors, and an Author may write multiple books, so if we serialize a book to JSON and include all of the information about all of its Authors, and then we serialize all of those, and then they all have information about their Books, so then we serialize all those…

An Author is very simple, the name of the author is the ID. That’s it. A Book is an ID, and a title. They have a ManyToMany relationship between them.

So we could POST a book like this, which would make a book, and map it to an author, which we may already have in the database, or which we might be making from her scratch:

POST /book
{
    "title": "Harry Potter and the Philosopher's Stone",
    "authors": [
        {
            "name": "JK Rowling"
        }
    ]
}

Works great! But then we would need to make one HTTP request per book, which could incur a performance constraint. So we want to have another endpoint for adding lots of them:

POST /books
[
    {
        "title": "Harry Potter and the Philosopher's Stone",
        "authors": [
            {
                "name": "JK Rowling"
            }
        ]
    },
    {
        "title": "Harry Potter and the Chamber of Secrets",
        "authors": [
            {
                "name": "JK Rowling"
            }
        ]
    }
]

Unfortunately this will be a problem, because Jackson will deserialize the two “JK Rowling” separately, and bomb out, and even though they are the exact same object, Jackson doesn’t understand how to map them together. My solution was to have the two objects simply resolve to be the same thing. The exact same object reference.

In the Entity:

    @JsonIdentityInfo(
            generator=ObjectIdGenerators.PropertyGenerator.class, 
            property="name", 
            scope = Author.class, 
            resolver = DedupingObjectIdResolver.class
    )

The ObjectIdResolver class:

package hello;

import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;

import java.util.HashMap;

public class DedupingObjectIdResolver extends SimpleObjectIdResolver {
    @Override
    public void bindItem(IdKey id, Object ob) {
        if (_items == null) {
            _items = new HashMap<>();
        }
        _items.put(id, ob);
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return new DedupingObjectIdResolver();
    }
}

I’ll upload the final project code in a sec here.

Thanks, its work for me

@cowtowncoder just common sense. I have a framework that sends JSONs like this abstract event:

{
  "id":1, 
  "createdBy":{"id":1}, 
  "participants":[
    {"id":1},
    {"id":2}
  ]
}

The id numbers on createdBy and participants are the same objects. Jackson would strip object with “id” part and put only number instead of object. This is a very useful thing (and I have no doubts it really is), but other frameworks that can produce and consume JSON’s (gson, for example) would be shocked by this. They don’t have this ‘forward references’ thing and just serialize objects as IDs as-is. On the deserializing side, however, Jackson can’t handle this.

Thank you both… I’m new and will attempt the fix Jfitz suggested.

Jackson allows you to specify different scopes with @JsonIdentityInfo, like so:

public class Container {
  @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class,property="@id",
       scope=OwnerScope.class)
  public User owner;

  @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class,property="@id",
       scope=UserScope.class)
  public User user;

Class used for scope has no semantic meaning, it’s simply just some value – and by default, it defaults to type of thing being referenced.

The challenge with XML is whether semantics of XmlId match with this or not.

While I understand that things may not work for your use case, or the way you want, it is good to keep in mind that there are many ways scoping could or should work for different users. Without having additional information on how scoping should work, a library needs to use its best judgment, and I really do not see anything actually better than using a per-type global scope.

As to JPA, other tools: they have their semantics, definitions; and likewise Bean Validation uses whatever rules you give. What they do has very little relevance for this case.

You are trying to use two objects with same object id – that would seem to be your bug right there. So you need to resolve the problem of producing duplicate ids. Jackson has no mechanism for resolving duplicate ids, and I don’t think that it something that should be allowed.

Test for XmlID shows the same problem, duplicate id (test I assume is one from JAXB annotations module).