2016-09-29 1 views
4

Je voudrais être en mesure de désérialiser un UnmodifiableSet avec la saisie par défaut activée. Pour ce faire, je l'ai créé un UnmodifiableSetMixin comme indiqué ci-dessous:Custom UnmodifiableSetMixin échoue dans Jackson 2.7+

NOTE: Vous pouvez trouver un projet minimal avec tout le code source pour reproduire ce problème à 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) {} 
} 

J'essaie alors d'utiliser cette désérialiser un ensemble vide.

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(); 
    } 
} 

Le test passe avec Jackson 2.6, mais ne parvient en utilisant Jackson 2.7+ avec la trace de pile suivante:

 
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) 

Quelqu'un peut-il me aider à résoudre le test Jackson 2.7+ (je aimez-vous travailler pour Jackson 2.8.3)?

+1

CURIOS juste si vous pouvez changer de 'UnmodifiableSetMixin (Set s) {}' 'à UnmodifiableSetMixin publique (Set s) {}' –

+0

Merci pour la réponse. Malheureusement, le test échoue toujours avec le même message d'erreur lorsque le mixin a un constructeur public. J'ai mis à jour l'exemple de code sur SO et dans le repo github pour refléter cela –

+0

Juste pour jouer, que se passe-t-il si vous ajoutez ** un nouveau 'public UnmodifiableSetMixin() {}' (constructeur non-args). Vous devriez en avoir deux. –

Répondre

2

Il s'avère qu'il s'agit d'une régression chez Jackson. J'ai créé https://github.com/FasterXML/jackson-databind/issues/1392 qui reconnaît le bug.

Une solution de contournement qui utilise un désérialiseur personnalisé m'a été fournie via #4078. Par exemple:

@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); 
    } 
} 
0

Pour que le mixage fonctionne, il doit y avoir un constructeur à un argument (*) sur cette classe interne; sinon, le mixage n'est pas associé.

Mais avez-vous absolument besoin de cette classe interne JDK? Si ce n'est pas le cas, vous pouvez ajouter un mappage pour indiquer que vous souhaitez utiliser l'une des implémentations Collection standard au lieu de la désérialisation à l'aide de SimpleModule.addAbstractTypeMapping(abstractType, concreteType), en enregistrant ce module.

(*) EDIT J'ai d'abord dit constructeur "zéro-argument"; mais le constructeur en question prend un argument.

+0

Pouvez-vous développer le constructeur de zéro argument? Voulez-vous dire que la classe est désérialisée ou mixin? Aucun sens n'a de sens puisque cela fonctionne dans Jackson <2.7 Pouvez-vous me montrer quel code doit être changé pour résoudre ce problème. Je dois pouvoir désérialiser UnmodifiableSet car la classe doit rester passive. Note voir le projet github lié pour exécuter le code réel –

+0

First; J'aurais dû dire "ctor à un argument", pas zéro. Et puis, je veux dire que la classe "cible" (celle pour laquelle le mix-in augmenterait conceptuellement) doit avoir des choses auxquelles les annotations de mix-in s'attacheraient. Les mix-ins ne provoquent aucune modification du bytecode, donc si la cible n'a pas de constructeur, les annotations de mix-ins ne seront pas utilisées. – StaxMan

+0

La cible (UnmodifiableSet) a un seul constructeur d'argument qui prend un Set (il fait partie de la distribution JDK donc vous pouvez voir par vous-même). –