@JsonIdentityReference не распознает равные значения - PullRequest
1 голос
/ 06 февраля 2020

Я пытаюсь сериализовать объект (Root), с некоторыми дублированными записями MyObject. Просто хочу сохранить все объекты один, я использую @JsonIdentityReference, который работает довольно хорошо.

Однако я понимаю, что он будет генерировать недесериализуемый объект, если есть равные объекты с разными ссылками. Интересно, есть ли в Джексоне конфигурация для изменения этого поведения, спасибо!

@Value
@AllArgsConstructor
@NoArgsConstructor(force = true)
class Root {
    private List<MyObject> allObjects;
    private Map<String, MyObject> objectMap;
}

@Value
@AllArgsConstructor
@NoArgsConstructor(force = true)
@JsonIdentityReference
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class MyObject {
    private String id;
    private int value;
}

public class Main {
    public static void main() throws JsonProcessingException {
        // Constructing equal objects
        val obj1 = new MyObject("a", 1);
        val obj2 = new MyObject("a", 1);
        assert obj1.equals(obj2);

        val root = new Root(
                Lists.newArrayList(obj1),
                ImmutableMap.of(
                        "lorem", obj2
                )
        );
        val objectMapper = new ObjectMapper();

        val json = objectMapper.writeValueAsString(root);
        // {"allObjects":[{"id":"a","value":1}],"objectMap":{"lorem":{"id":"a","value":1}}}
        // Note here both obj1 and obj2 are expanded.

        // Exception: Already had POJO for id 
        val deserialized = objectMapper.readValue(json, Root.class);
        assert root.equals(deserialized);
    }
}

Я использую Джексон 2.10.

Полная трассировка стека:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]] (through reference chain: Root["objectMap"]->java.util.LinkedHashMap["lorem"]->MyObject["id"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1714)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1257)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:157)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:527)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:364)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
    at Main.main(Main.java:53)
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [[ObjectId: key=a, type=com.fasterxml.jackson.databind.deser.impl.PropertyBasedObjectIdGenerator, scope=java.lang.Object]]
    at com.fasterxml.jackson.annotation.SimpleObjectIdResolver.bindItem(SimpleObjectIdResolver.java:24)
    at com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.bindItem(ReadableObjectId.java:57)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeSetAndReturn(ObjectIdValueProperty.java:101)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty.deserializeAndSet(ObjectIdValueProperty.java:83)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369)
    ... 14 more

1 Ответ

1 голос
/ 06 февраля 2020

Как я упоминал ранее, эта настройка работает только в случае obj1 == obj2, поскольку два объекта с одинаковым идентификатором должны быть идентичны. В этом случае второй объект также будет расширяться net во время сериализации (alwaysAsId = false расширяет только первый объект).

Однако, если вы хотите иметь эту настройку и хорошо справляться с сериализацией, вы можете использовать собственный Resolver для десериализации, который хранит один экземпляр на ключ:

@JsonIdentityReference(alwaysAsId = false)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = CustomScopeResolver.class)
static class MyObject {

    private String id;
    // ...
}

class CustomScopeResolver implements ObjectIdResolver {

    Map<String, MyObject> data = new HashMap<>();

    @Override
    public void bindItem(final IdKey id, final Object pojo) {
        data.put(id.key.toString(), (MyObject) pojo);
    }

    @Override
    public Object resolveId(final IdKey id) {
        return data.get(id.key);
    }

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

    @Override
    public boolean canUseFor(final ObjectIdResolver resolverType) {
        return false;
    }

}

NEW EDIT: Очевидно, это очень просто: просто включите objectMapper.configure(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID, true);, чтобы DefaultSerializerProvider использовал обычную Hashmap вместо IdentityHashMap для управления сериализованными компонентами.

УСТАРЕЛО: Обновление для сериализации: Этого можно добиться, добавив пользовательский поставщик сериализации:

class CustomEqualObjectsSerializerProvider extends DefaultSerializerProvider {

    private final Collection<MyObject> data = new HashSet<>();
    private final SerializerProvider src;
    private final SerializationConfig config;
    private final SerializerFactory f;

    public CustomEqualObjectsSerializerProvider(
            final SerializerProvider src,
            final SerializationConfig config,
            final SerializerFactory f) {
        super(src, config, f);
        this.src = src;
        this.config = config;
        this.f = f;
    }

    @Override
    public DefaultSerializerProvider createInstance(final SerializationConfig config, final SerializerFactory jsf) {
        return new CustomEqualObjectsSerializerProvider(src, this.config, f);
    }

    @Override
    public WritableObjectId findObjectId(final Object forPojo, final ObjectIdGenerator<?> generatorType) {
        // check if there is an equivalent pojo, use it if exists
        final Optional<MyObject> equivalentObject = data.stream()
                .filter(forPojo::equals)
                .findFirst();

        if (equivalentObject.isPresent()) {
            return super.findObjectId(equivalentObject.get(), generatorType);
        } else {
            if (forPojo instanceof MyObject) {
                data.add((MyObject) forPojo);
            }
            return super.findObjectId(forPojo, generatorType);
        }

    }

}

@Test
public void main() throws IOException {
    // Constructing equal objects
    final MyObject obj1 = new MyObject();
    obj1.setId("a");

    final MyObject obj2 = new MyObject();
    obj2.setId("a");
    assert obj1.equals(obj2);

    final Root root = new Root();
    root.setAllObjects(Collections.singletonList(obj1));
    root.setObjectMap(Collections.singletonMap(
            "lorem", obj2));
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializerProvider(
            new CustomEqualObjectsSerializerProvider(
                    objectMapper.getSerializerProvider(),
                    objectMapper.getSerializationConfig(),
                    objectMapper.getSerializerFactory()));
    final String json = objectMapper.writeValueAsString(root);
    System.out.println(json); // second object is not expanded!
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...