Пользовательский UnmodifiableSetMixin не работает в Jackson 2.7+

Я хотел бы иметь возможность десериализовать UnmodifiableSet с включенной типизацией по умолчанию. Для этого я создал UnmodifiableSetMixin, как показано ниже:

ПРИМЕЧАНИЕ. Вы можете найти минимальный проект со всем исходным кодом для воспроизведения этой проблемы по адресу https://github.com/rwinch/jackson-unmodifiableset-mixin

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import java.util.Set;

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
public abstract class UnmodifiableSetMixin {

    @JsonCreator
    public UnmodifiableSetMixin(Set<?> s) {}
}

Затем я пытаюсь использовать это для десериализации пустого набора.

public class UnmodifiableSetMixinTest {
    static final String EXPECTED_JSON = "[\"java.util.Collections$UnmodifiableSet\",[]]";

    ObjectMapper mapper;

    @Before
    public void setup() {
        mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        mapper.addMixIn(Collections.unmodifiableSet(Collections.<String>emptySet()).getClass(), UnmodifiableSetMixin.class);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void read() throws Exception {
        Set<String> foo = mapper.readValue(EXPECTED_JSON, Set.class);
        assertThat(foo).isEmpty();
    }
}

Тест проходит с Jackson 2.6, но не проходит с Jackson 2.7+ со следующей трассировкой стека:

java.lang.IllegalStateException: No default constructor for [collection type; class java.util.Collections$UnmodifiableSet, contains [simple type, class java.lang.Object]]
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:240)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:110)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromArray(AsArrayTypeDeserializer.java:50)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserializeWithType(CollectionDeserializer.java:310)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:42)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3788)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2779)
    at sample.UnmodifiableSetMixinTest.read(UnmodifiableSetMixinTest.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Может ли кто-нибудь помочь мне исправить тест для Jackson 2.7+ (я бы хотел, чтобы он работал для Jackson 2.8.3)?


person Rob Winch    schedule 29.09.2016    source источник
comment
Просто любопытно, если можно сменить UnmodifiableSetMixin(Set<?> s) {} на public UnmodifiableSetMixin(Set<?> s) {}   -  person Manuel Jordan    schedule 29.09.2016
comment
Спасибо за ответ. К сожалению, тест по-прежнему не проходит с тем же сообщением об ошибке, когда миксин имеет общедоступный конструктор. Я обновил образец кода на SO и в репозитории github, чтобы отразить это   -  person Rob Winch    schedule 29.09.2016
comment
Просто для игры, что произойдет, если вы добавите новый public UnmodifiableSetMixin() {} (конструктор без аргументов). У вас должно быть два.   -  person Manuel Jordan    schedule 29.09.2016
comment
Это не удается, потому что java.util.Collections$UnmodifiableSet не имеет конструктора по умолчанию. Если вы хотите попробовать эти идеи самостоятельно, вы можете клонировать очень простой проект, который я предоставил на github. com / rwinch / jackson-unmodifiableset-mixin   -  person Rob Winch    schedule 29.09.2016
comment
Извините, только что заметил добавление конструктора. Это терпит неудачу точно так же.   -  person Rob Winch    schedule 29.09.2016
comment
Какая версия Джексона? Вы пробовали последнюю 2.7 (2.7.8)?   -  person StaxMan    schedule 30.09.2016
comment
Я пробовал 2.8.3 и не работает   -  person Rob Winch    schedule 30.09.2016


Ответы (2)


Оказывается, это регресс Джексона. Я создал https://github.com/FasterXML/jackson-databind/issues/1392, который подтверждает наличие ошибки.

Обходной путь с использованием настраиваемого десериализатора был предоставлен мне через # 4078. Например:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonDeserialize(using = UnmodifiableSetDeserializer.class)
public abstract class UnmodifiableSetMixin {
    @JsonCreator
    public UnmodifiableSetMixin(Set<?> s) {}
}

public class UnmodifiableSetDeserializer extends JsonDeserializer<Set> {

    @Override
    public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        JsonNode node = mapper.readTree(jp);
        Set<Object> resultSet = new HashSet<Object>();
        if (node != null) {
            if (node instanceof ArrayNode) {
                ArrayNode arrayNode = (ArrayNode) node;
                Iterator<JsonNode> nodeIterator = arrayNode.iterator();
                while (nodeIterator.hasNext()) {
                    JsonNode elementNode = nodeIterator.next();
                    resultSet.add(mapper.readValue(elementNode.toString(), Object.class));
                }
            } else {
                resultSet.add(mapper.readValue(node.toString(), Object.class));
            }
        }
        return Collections.unmodifiableSet(resultSet);
    }
}
person Rob Winch    schedule 04.10.2016
comment
Этот обходной путь работает для jackson-databind v2.8.9. Спасибо тебе большое. - person SRH LABS; 21.03.2018

Чтобы смешивание работало, в этом внутреннем классе должен быть конструктор с одним аргументом (*); в противном случае подмешивание не связывается.

Но действительно ли вам необходимо использовать этот внутренний класс JDK? Если нет, вы можете добавить отображение, чтобы указать, что вы хотите использовать одну из стандартных реализаций Collection вместо десериализации с использованием SimpleModule.addAbstractTypeMapping(abstractType, concreteType), регистрируя этот модуль.

(*) РЕДАКТИРОВАТЬ Сначала я сказал конструктор с "нулевым аргументом"; но рассматриваемый конструктор принимает один аргумент.

person StaxMan    schedule 29.09.2016
comment
Можете ли вы расширить конструктор с нулевым аргументом? Вы имеете в виду десериализуемый класс или миксин? Ни то, ни другое не имеет смысла, поскольку это работает в Jackson ‹2.7. Можете ли вы показать мне, какой код нужно изменить, чтобы исправить это. Я должен иметь возможность десериализовать UnmodifiableSet, потому что класс должен оставаться пассивным. Обратите внимание: см. Связанный проект github для запуска фактического кода - person Rob Winch; 30.09.2016
comment
Первый; Я должен был сказать ctor с одним аргументом, а не с нулем. И затем я имею в виду, что целевой класс (тот, для которого микширование будет концептуально расширено) должен иметь вещи, к которым будут прикрепляться аннотации из микширования. Подмешивания не вызывают никаких изменений байт-кода, поэтому, если цель не имеет конструктора, аннотации подмешивания не будут использоваться. - person StaxMan; 30.09.2016
comment
У цели (UnmodifiableSet) действительно есть конструктор с одним аргументом, который принимает Set (он является частью дистрибутива JDK, поэтому вы можете убедиться в этом сами). - person Rob Winch; 30.09.2016
comment
@RobWinch хорошо, тогда это не должно быть проблемой. Я только что упомянул, поскольку это могло быть проблемой - хотя, очевидно, не должно отличаться ч / б версии Джексона на одном и том же JDK. - person StaxMan; 30.09.2016