question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Deserializing JSON into class where field type is marker interface for different enums fails

See original GitHub issue

jackson-databind version

2.8.2 (com.fasterxml.jackson.core:jackson-databind:2.8.2)


Problem

I have found a bug related to using addAbstractTypeMapping when adding more than one enum class for same marker interface:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums2 from String “A”: value not one of declared Enum instance names: [B] at [Source: {“value”:“A”}; line: 1, column: 10](through reference chain: com.sample.Container[“value”])

Example

When I have classes:

    public static class Container { // <- 
        private InterfaceForEnums enumValue;
        // Getters/setters left out for brevity
    }

        // marker interface for enums
    interface InterfaceForEnums {
    }

    public static enum Enums1 implements InterfaceForEnums {
        A
    }

    public static enum Enums2 implements InterfaceForEnums {
        B
    }

and when I want to deserialize {"value":"A"}, then registering enum classes for interface in following order would result in failure during deserialization:

        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        // order shouldn't matter, but matters: only works for values of enum2 (as it is added last)
        simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, Enums1.class); 
        simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, Enums2.class);
        objectMapper.registerModule(simpleModule);
...
objectMapperNotOK.readValue(json, Container.class)

but when registering enums in opposite order, then deserialization works for values of Enum1 (but not for values of Enum2).

I have created JUnit test to show that both enum constants can be deserialized, but with different ObjectMapper configuration, that makes it impossible to use in situation where the field could have values form either enums:

package com.sample;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;

public class JacksonEnumDeserialization {
    public static final String JSON_A = "{\"value\":\"A\"}";
    public static final String JSON_B = "{\"value\":\"B\"}";

    interface InterfaceForEnums {
    }

    public static enum Enums1 implements InterfaceForEnums {
        A
    }

    public static enum Enums2 implements InterfaceForEnums {
        B
    }

    @Data
    public static class Container {
        private InterfaceForEnums value;
        // Getters/setters generated with lombok (using @Data)
    }

    private ObjectMapper createObjectMapper(
            Class<? extends InterfaceForEnums>... classes) {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        for (Class<? extends InterfaceForEnums> enumClass : classes) {
            simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, enumClass);
        }
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }

    @Test
    public void testSerialization() throws Exception {
        ObjectMapper objectMapper = createObjectMapper();
        Assert.assertEquals(JSON_A, objectMapper.writeValueAsString(newContainer(Enums1.A)));
        Assert.assertEquals(JSON_B, objectMapper.writeValueAsString(newContainer(Enums2.B)));
    }

    private Container newContainer(InterfaceForEnums e) {
        Container object = new Container();
        object.setValue(e);
        return object;
    }

    @Test
    public void testDeserializationWithEnum1OK() throws Exception {
        ObjectMapper objectMapperOK = createObjectMapper(Enums2.class, Enums1.class);
        Assert.assertEquals(newContainer(Enums1.A), objectMapperOK.readValue(JSON_A, Container.class));
    }

    @Test
    public void testDeserializationWithEnum1NotOK() throws Exception {
        ObjectMapper objectMapperNotOK = createObjectMapper(Enums1.class, Enums2.class);
        //      FIXME com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums2 from String "A": value not one of declared Enum instance names: [B]
        //      at [Source: {"value":"A"}; line: 1, column: 10] (through reference chain: com.sample.Container["value"])
        Assert.assertEquals(newContainer(Enums1.A), objectMapperNotOK.readValue(JSON_A, Container.class));
    }

    @Test
    public void testDeserializationWithEnum2OK() throws Exception {
        ObjectMapper objectMapperOK = createObjectMapper(Enums1.class, Enums2.class);
        Assert.assertEquals(newContainer(Enums2.B), objectMapperOK.readValue(JSON_B, Container.class));
    }

    @Test
    public void testDeserializationWithEnum2NotOK() throws Exception {
        ObjectMapper objectMapperNotOK = createObjectMapper(Enums2.class, Enums1.class);
        //      FIXME com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums1 from String "B": value not one of declared Enum instance names: [A]
        //      at [Source: {"value":"B"}; line: 1, column: 10] (through reference chain: com.sample.Container["value"])
        Assert.assertEquals(newContainer(Enums2.B), objectMapperNotOK.readValue(JSON_B, Container.class));
    }

}

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
cowtowncodercommented, Sep 8, 2016

Thank you for reporting this and providing a reproduction! I hope to look into this soon.

0reactions
cowtowncodercommented, Sep 12, 2016

@atsu85 That is not how mapping works: there can only be one explicit mapping. I may try to improve documentation in case it may have lead to wrong conclusion.

Glad to know you got it working; I do think that you need a custom deserializer here, that can use proper heuristics.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How deserialize based on information available in the parent ...
I am using Jackson to deserialize a number of different implementations of the Product interface. These product implementations have different ...
Read more >
Serializing and Deserializing Enumerations with Json.NET
This article shows you how to serialize and deserialize enums with Json.NET. If you write a client for a RESTful API, then you...
Read more >
Serialization
The deserialization process will key on this marker to maintain object references. Is the object a (JSON) primitive type or an enumeration?
Read more >
Serialization with Jackson - Documentation - Akka
In many cases ordinary classes can be serialized by Jackson ... would be to name the marker interface CborSerializable or JsonSerializable .
Read more >
Package google.protobuf | Protocol Buffers
If the embedded message type is well-known and has a custom JSON ... int32, The index of the field type in Type.oneofs ,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found