Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
luisvt opened this issue Jul 17, 2013 · 35 comments

Comments

@luisvt
Copy link

luisvt commented Jul 17, 2013

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?

@cowtowncoder
Copy link
Member

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

@cowtowncoder
Copy link
Member

User mailing list found at:

https://groups.google.com/forum/#!forum/jackson-user

@ageorgousakis
Copy link

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!

@ageorgousakis
Copy link

Add this test to TestXmlID2 tests. It will fail

public void testDeserializeTwoObjectsWithSameId() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    // but then also variant where ID is ALWAYS used for XmlID / XmlIDREF
    mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(mapper.getTypeFactory()));
    List<User> users = getUserList();
    String json = "[{\"id\":11,\"username\":\"11\",\"email\":\"[email protected]\",\"department\":{\"id\":9,\"name\":\"department9\",\"employees\":[11,22]}}," +
            "{\"id\":22,\"username\":\"22\",\"email\":\"[email protected]\",\"department\":{\"id\":9,\"name\":\"department9\",\"employees\":[11,22]}}," +
            "{\"id\":33,\"username\":\"33\",\"email\":\"[email protected]\",\"department\":null}]";
    List<User> result = mapper.readValue(json, new TypeReference<List<User>>() {
    });
    assertEquals(3, result.size());
    assertEquals(Long.valueOf(11), result.get(0).id);
    assertEquals(Long.valueOf(22), result.get(1).id);
    assertEquals(Long.valueOf(33), result.get(2).id);
    // However, there is no way to resolve those back, without some external mechanism...
}

@cowtowncoder
Copy link
Member

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

@ageorgousakis
Copy link

This is not a bug. You have two users that are in the same department. When Jackson is trying to deserialize the data you get this error because it finds the same department id in two users. The serialization works fine.

@cowtowncoder
Copy link
Member

Input JSON has two full department definitions, both with id 9. Use of JSON Object is taken to mean POJO, and references MUST use simple scalar values. So from Jackson perspective you do have conflicting definitions, two departments that claim to have same id.

I understand that you might want behavior to be such that full objects could be passed, and somehow Jackson figured out which one to use. But this is not how Object Id works, which is why I do not think this is a bug as is.

@ageorgousakis
Copy link

Why you don't think this is a bug. If I run this test putting only the id of department in the second instance it will not fail. But as you understand we want to pass the object with all the values or only with the id from the client side to the JSON provider. If you cannot resolve it, can you suggest an alternative or a way to disable this feature?

@test
public void testDeserializeTwoObjectsWithSameId() throws Exception {
ObjectMapper mapper = new ObjectMapper();
// but then also variant where ID is ALWAYS used for XmlID / XmlIDREF
mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(mapper.getTypeFactory()));
List users = getUserList();
String json = "[{"id":11,"username":"11","email":"[email protected]","department":{"id":9,"name":"department9","employees":[11,22]}}," +
"{"id":22,"username":"22","email":"[email protected]","department":9}," +
"{"id":33,"username":"33","email":"[email protected]","department":null}]";
List result = mapper.readValue(json, new TypeReference<List>() {
});
assertEquals(3, result.size());
assertEquals(Long.valueOf(11), result.get(0).id);
assertEquals(Long.valueOf(22), result.get(1).id);
assertEquals(Long.valueOf(33), result.get(2).id);
// However, there is no way to resolve those back, without some external mechanism...
}

@cowtowncoder
Copy link
Member

I explained it already. Logic for choosing whether a given value is a full object, or object reference, is simple:

  1. If value is a JSON String or Number, it is expected to be a reference: id defines as part of a full object.
  2. Otherwise, we have a full value, that has (unique) id in it, to be possibly referenced by something else.

What you can not do is expect some kind of magic to figure out that JSON Objects sometimes are full values, and sometimes references. This is not supported by object id handling.

If you do not want to use Object Ids, then remove @JsonIdentityInfo.

@ageorgousakis
Copy link

My problem is that in you validation for duplicate ids you have one scope for all objects inside the root object you receive. It's object inside the root object and each object in a collection item of root object should have his own scope. You throw this validation because in SimpleObjectIdResolver you keep all the inner objects of root object which is totally wrong.
If you take this object with the same integrity constraints and try it to save in a database using JPA (Hibernate, etc) it will not throw any error. If you pass this object to an XML validator (XmlSpy, Oxygen, ..) it will not throw any error. If I pass this object to a Bean Validation framework it will not throw any error. Only Jackson library decides that this an error and you say you cannot do anything so remove this constraint if you want to use this library.
Nice.

@cowtowncoder
Copy link
Member

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.

@jfitzpatrick99
Copy link

I also ran into this issue and one work-around I found is to supply a custom DefaultDeserializationContext implementation to the object mapper. Specifically I implemented the "findObjectId" method to not bother caching the objects it finds but instead to just simply return a new "ReadableObjectId" instance each time.

btw, The main motivation I see for such a use case is where a application is sending back denormalized json documents, which is in fact my use case. It would be nice if a "DeserializationFeature" was added to prevent the validation check from causing the deserializer to blow-up.

@cowtowncoder
Copy link
Member

@jfitzpatrick99 if you do not want Jackson to manage Object Ids, you probably should not indicate those fields are ids -- the only reason for annotations is to automatically connect object instances via ids; and for that reason ids absolutely must be retained and resolved; and duplicate ids are errors unless used in different scopes.

The original request of somehow automatically de-duping objects is something Jackson does not do, and is unlikely to support.

@jfitzpatrick99
Copy link

In my case, I am not interested in automatically de-duping objects, I just don't want Jackson to blow-up during deserialization if it encounters an object with a duplicate ID.

My use case is that I want to be able to deserialize data that may either contain references to objects only, e.g. references that are always serialized by ID only, or references that are expanded in-line in the json document. To accomplish this, I wrote a custom Jackson serializer that supports in-line expansion of selected referenced entities which is enabled or disabled based on the value of a query parameter. By default, I have Jackson configured to always serialize references by ID only. It would be nice if Jackson supported the ability to ignore duplicates to help with this use case. I suppose I could do what you suggest and remove the @JsonIdentityInfo annotation but then I have to implement more custom logic to serialize the ID field as a reference myself.

If I understand how Jackson does scoping correctly, it does not seem possible to use scoping to make deserialization work for the case where a list of objects contains references to nested objects where two or more nested objects share the same ID.

@cowtowncoder
Copy link
Member

Correct. Jackson Object Id handling is design explicitly to support Object Identity. It will not currently bend to cases of "maybe unique, maybe not" hybrid schemes.

Assuming this was to be improved to allow other schemes: how should it work? Without retaining ids at all, it seems of little use -- what would ids be resolved to? With multiple choices, however, which one should it link to? First one in document order?

I will reopen this, since maybe there is something to improve here, if this really is a commonly used pattern. I did not think it was, but since this keeps on coming up...

@cowtowncoder cowtowncoder reopened this Jan 22, 2015
@jfitzpatrick99
Copy link

For my particular use case, the resulting deserialized object graph is denormalized so that I literally end up with multiple instances of the same object. This works well for me and might work well for others. If this approach were to be incorporated into Jackson, I would suggest adding a "DeserializationFeature" such as "DENORMALIZE_DUPLICATE_OBJECT_IDS".

Obviously the default would be to have the feature disabled but if enabled, Jackson would simply ignore objects with same ID during deserialization and create multiple object instances representing the same object.

For implementation, it would just be necessary to change the implementation of the DefaultDeserializationContext.findObjectId according to whether the feature is enabled. Although I am not super familiar with the Jackson code base so there may be a better place to make this change.

@cowtowncoder
Copy link
Member

Ok let me try to rephrase this: are you NEVER getting id back? And the only reason to enable Object Id handling is for serialization, where you do want to remove duplicate; but some other systems produce different kind of output?
If all you wanted was to produce objects on serialization, you would want to use @JsonIdentityReference.alwaysAsId for referring properties:

http://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonIdentityReference.html

But if you want the first instance to be serialized as full object, secondaries as ids, then @JsonIdentityInfo makes sense.

As to ignored findObjectId(): what I expect to happen is simply this: someone else, somewhere, will want both duplicates and resolution. If no references are retained, then either references throw an exception, or get deserialized as nulls (with one of existing settings).
For that reason maybe it'd be just better to keep track of references, but ignore overwrites (i.e. retain first reference registered). It won't matter for use cases where ids are not needed (save for some memory overhead).

@jfitzpatrick99
Copy link

Ok let me try to rephrase this: are you NEVER getting id back? And the only reason to enable Object Id handling is for serialization, where you do want to remove duplicate; but some other systems produce different kind of output?

For our application, we either send back the id of the referenced object only, or if requested by the client, we "expand" the referenced object so that the entire object is returned. For example, in our application users own an object called stream. Streams can be made public or remain private. If a stream is made public then other users may access the stream. By default, when a list of streams is serialized, I want that only the id of the owning user is returned. However, if a stream has been made public, it is required for us to also include (limited) information about the user who owns the stream. This means that when deserializing this data, we will likely end up with duplicate user objects in the resulting json document. In our case, the user objects will actually be expanded partial representations of the user object for security purposes. To meet these requirements, it is convenient to simply add @JsonIdentityReference(alwaysAsId = true) to the user reference and then override this behaviour in a custom serializer according to the value of a query parameter. Maybe there is a better way of achieving what I want to do? Incidentally I don't like the idea of only serializing the full object for the first user object having a given ID because that makes it more difficult for the client to process the data, i.e. client now has to keep track of which objects having a given ID have been received and reestablish the links. I am ok with the memory/increased data size trade-off for the reduction in complexity.

As to ignored findObjectId(): what I expect to happen is simply this: someone else, somewhere, will want both duplicates and resolution. If no references are retained, then either references throw an exception, or get deserialized as nulls (with one of existing settings).

Supporting both duplicates and resolution is not what I need but I agree that someone may want this. I think that for now, simply supporting duplicates would be sufficient.

For that reason maybe it'd be just better to keep track of references, but ignore overwrites (i.e. retain first reference registered). It won't matter for use cases where ids are not needed (save for some memory overhead).

Exactly, except I think it would probably be best to add a deserialization feature mutually exclusive to "DENORMALIZE_DUPLICATE_OBJECT_IDS" that enables this behavior.

@aokegbile
Copy link

@jfitzpatrick99
How did you resolve this issue? Is it resolved? I'm having the same issue. I am considering manual "de"hydrating of the json object before use.

@jfitzpatrick99
Copy link

@aokegbile, The issue has not been addressed in Jackson but there is a work-around. What you can do is extend the com.fasterxml.jackson.databind.deser.DefaultDeserializationContext class and override the findObjectId method to always return a new ReadableObjectId instance.

@Override
public ReadableObjectId findObjectId(final Object id, final ObjectIdGenerator<?> generator) {
    return new ReadableObjectId(id);
}

Then you just need to configure the object mapper to use your custom DefaultDeserializationContext implementation. This can be done by supplying your custom context implementation to the object mapper constructor.

Hope that helps.

@cowtowncoder
Copy link
Member

@aokegbile Since this is not a bug but a feature, I closed this particular issue. But there is (or should be?) a separate issue for adding a feature to allow easily altering the behavior, for cases where automated strict matching is not desired.

@aokegbile
Copy link

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

@kloudsamurai
Copy link

Hey guys. Was something ever introduced into the library to accommodate this use case or is the fix from Jfitz still the solution people are using?

@cowtowncoder
Copy link
Member

I don't think anything was yet added.

@nareshr8
Copy link

@jfitzpatrick99 'findObjectId' method is not getting called. Is there something else that I have to do?

@jfitzpatrick99
Copy link

@nareshr8, Before I can help you, you need to supply more information about what you have tried.

The check list is:

  1. Extend DefaultDeserializationContext class and override the "findObjectId" method.
  2. Ensure that your custom DefaultDeserializationContext class is being passed to the ObjectMapper constructor.

Note that there are other methods in the ObjectMapper class that can change the "DefaultDeserializationContext" instance in use. You should also make that sure than one of these methods is not causing the context to be swapped out underneath you. To mitigate this you should also override the fluent "DefaultDeserializationContext" factory methods.

If none of that helps, I suggesting using the debugger to confirm that your object mapper is using the instance you expect and go from there.

@Kaned1as
Copy link

Ran into this not so long ago.
I second "DENORMALIZE_DUPLICATE_OBJECT_IDS" feature.

@cowtowncoder
Copy link
Member

@Adonai quick question here: in your case, what is the reason for using @JsonIdentityInfo, if ids are known to have duplicates within scope? Or is this via JAXB annotations (@XmlId)?

I am still trying to fully understand actual use case. It is possible there are multiple ones, but at least one of them. I do understand the case of id-strings not being unique (but not necessarily ever resolved); I can understand different scopes (which can handled via @JsonIdentityInfo(scope=...))), but not yet case where both are true.

@Kaned1as
Copy link

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

@cowtowncoder
Copy link
Member

@Adonai Thank you for clarification. Ok so the problem is not duplicates per se, but the representation of ids. Thing is, Jackson does not thing you are passing an ID -- it is an Object, and Jackson does not support composite Object keys, mostly because there is no simple and reliable way to detect difference between Objects and references to Objects.

This problem will not go away simply by allowing duplicate ids. Rather it will create "empty" Object values, which is unlikely to be what users want.
So if this is the use case, solution is not to allow duplicate ids: it would be to recognize specific JSON Object as Object Reference, and not as Object Value.

Now, for JSOG, there is an actual extension that can handle specific kind of "mini-object", where there is exactly one key/value pair, and key is fixed to specific value. This mechanism could be used to handle usage described above.

If this is what is wanted, a new issue should be filed, and we could figure out how to support that via annotations and/or API.

@Kaned1as
Copy link

@cowtowncoder you got me absolutely right. I'll file a different issue tomorrow.
And thanks for providing a look from inside for this, now I think I understand the inner logic a bit more.

@cowtowncoder
Copy link
Member

Link to Jackson JSOG module:

https:/jsog/jsog-jackson

@maludwig
Copy link

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.

@maludwig
Copy link

I've made a Gist of the solution I made:
https://gist.github.com/maludwig/12c168ad0b610696b2ca89124547f404

@fauzanss
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants