Skip to content

Deserialization of class with generic collection inside depends on how is was deserialized first time #676

@lunaticare

Description

@lunaticare

Hi everyone!

After migration from Jackson 1.9.x to 2.x (the problem can be reproduced in 2.5.0) I've got a problem deserializing generic collections.
Here's the test that documents the behavour.
Am I doing something wrong?

package com.fasterxml.jackson.databind.test;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;

import java.io.IOException;
import java.util.Date;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

/**
 * Deserialization of class with generic collection inside depends on how is was deserialized first time.
 */
public class JacksonPolymorphicDeSerFailTest {
    private static final int TIMESTAMP = 123456;
    private final MapContainer originMap =
            new MapContainer(ImmutableMap.<String, Object>of("DateValue", new Date(TIMESTAMP)));

    /**
     * If the class was first deserialized as polymorphic field,
     * deserialization will fail at complex type.
     */
    @Test
    public void testFactorsSerializationFail() throws IOException, JSONException {
        ObjectMapper mapper = new ObjectMapper();

        // incorrect
        MapContainer deserMapBad = createDeSerMapContainer(originMap, mapper);
        assertNotEquals(originMap, deserMapBad);
        assertEquals(ImmutableList.of("java.util.Date", TIMESTAMP),
                deserMapBad.getMap().get("DateValue"));

        // incorrect again
        assertNotEquals(originMap, mapper.readValue(mapper.writeValueAsString(originMap),
                MapContainer.class));
    }

    /**
     * If the class was first deserialized as is,
     * deserialization will work correctly.
     */
    @Test
    public void testFactorsSerializationMagicGood() throws IOException, JSONException {
        ObjectMapper mapper = new ObjectMapper();
        // commenting out the following statement will fail the test
        assertEquals(new MapContainer(ImmutableMap.<String, Object>of("1", 1)),
                mapper.readValue(
                        mapper.writeValueAsString(new MapContainer(ImmutableMap.<String, Object>of("1", 1))),
                        MapContainer.class));

        MapContainer deserMapGood = createDeSerMapContainer(originMap, mapper);
        // correct
        assertEquals(originMap, deserMapGood);
        assertEquals(new Date(TIMESTAMP), deserMapGood.getMap().get("DateValue"));

        // correct again
        assertEquals(originMap, mapper.readValue(mapper.writeValueAsString(originMap), MapContainer.class));
    }

    private static MapContainer createDeSerMapContainer(MapContainer originMap, ObjectMapper mapper)
            throws JSONException, IOException {
        PolymorphicValueWrapper result = new PolymorphicValueWrapper();
        result.setValue(originMap);
        String json = mapper.writeValueAsString(result);
        System.out.println("Original map json: " + json);
        JSONAssert.assertEquals("{\"value\":{\"@class\":"
                        + "\"com.fasterxml.jackson.databind.test.JacksonPolymorphicDeSerFailTest$MapContainer\","
                        + "\"map\":{\"DateValue\":[\"java.util.Date\",123456]}}}",
                new JSONObject(json), true);
        PolymorphicValueWrapper deserializedResult = mapper.readValue(json, PolymorphicValueWrapper.class);
        System.out.println("Deserialized map json: " + mapper.writeValueAsString(deserializedResult));
        return (MapContainer) deserializedResult.getValue();
    }

    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
    public static class MapContainer {
        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
                include = JsonTypeInfo.As.PROPERTY,
                property = "@class")
        private Map<String, Object> map;

        public MapContainer() { }

        public MapContainer(Map<String, Object> map) {
            this.map = map;
        }

        public Map<String, Object> getMap() {
            return map;
        }

        public void setMap(Map<String, Object> map) {
            this.map = map;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            MapContainer that = (MapContainer) o;

            if (map != null ? !map.equals(that.map) : that.map != null) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            return map != null ? map.hashCode() : 0;
        }

        @Override
        public String toString() {
            return "MapContainer{" +
                    "map=" + map +
                    '}';
        }
    }

    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
    public static class PolymorphicValueWrapper {
        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
                include = JsonTypeInfo.As.PROPERTY,
                property = "@class")
        private Object value;

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions