JavaLangObjectDeserializationTest.java

package tools.jackson.databind.deser.jdk;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import org.junit.jupiter.api.Test;

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

import tools.jackson.core.*;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.type.TypeReference;

import tools.jackson.databind.*;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.deser.std.StdScalarDeserializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;

import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import static tools.jackson.databind.testutil.DatabindTestUtil.*;

/**
 * Unit tests for verifying "raw" (or "untyped") data binding from JSON to JDK objects;
 * one that only uses core JDK types; wrappers, Maps and Lists.
 */
public class JavaLangObjectDeserializationTest
{
    static class UCStringDeserializer
        extends StdScalarDeserializer<String>
    {
        public UCStringDeserializer() { super(String.class); }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) {
            return p.getString().toUpperCase();
        }
    }

    static class CustomNumberDeserializer
        extends StdScalarDeserializer<Number>
    {
        protected final Integer value;

        public CustomNumberDeserializer(int nr) {
            super(Number.class);
            value = nr;
        }

        @Override
        public Number deserialize(JsonParser p, DeserializationContext ctxt) {
            return value;
        }
    }

    // Let's make this Contextual, to tease out cyclic resolution issues, if any
    static class ListDeserializer extends StdDeserializer<List<Object>>
    {
        public ListDeserializer() { super(List.class); }

        @Override
        public List<Object> deserialize(JsonParser p, DeserializationContext ctxt)
        {
            ArrayList<Object> list = new ArrayList<Object>();
            while (p.nextValue() != JsonToken.END_ARRAY) {
                list.add("X"+p.getString());
            }
            return list;
        }

        @Override
        public ValueDeserializer<?> createContextual(DeserializationContext ctxt,
                BeanProperty property)
        {
            // For now, we just need to access "untyped" deserializer; not use it.

            /*ValueDeserializer<Object> ob = */
            ctxt.findContextualValueDeserializer(ctxt.constructType(Object.class), property);
            return this;
        }
    }

    static class YMapDeserializer extends StdDeserializer<Map<String,Object>>
    {
        public YMapDeserializer() { super(Map.class); }

        @Override
        public Map<String,Object> deserialize(JsonParser p, DeserializationContext ctxt)
        {
            Map<String,Object> map = new LinkedHashMap<String,Object>();
            while (p.nextValue() != JsonToken.END_OBJECT) {
                map.put(p.currentName(), "Y"+p.getString());
            }
            return map;
        }
    }

    static class DelegatingUntyped {
        protected Object value;

        @JsonCreator // delegating
        public DelegatingUntyped(Object v) {
            value = v;
        }
    }

    static class WrappedPolymorphicUntyped {
        @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS)
        public Object value;

        protected WrappedPolymorphicUntyped() { }
        public WrappedPolymorphicUntyped(Object o) { value = o; }
    }

    // [databind#1460]
    static class WrappedUntyped1460 {
        public Object value;
    }

    // [databind#2115]
    static class SerContainer {
        public java.io.Serializable value;
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    private final JsonMapper MAPPER = newJsonMapper();
    private final ObjectReader OBJECT_READER = MAPPER.readerFor(Object.class);

    @SuppressWarnings("unchecked")
    @Test
    public void testSampleDoc() throws Exception
    {
        final String JSON = SAMPLE_DOC_JSON_SPEC;

        // To get "untyped" Mapping (to Maps, Lists, instead of beans etc),
        // we'll specify plain old Object.class as the target.
        Object root = MAPPER.readValue(JSON, Object.class);

        assertInstanceOf(Map.class, root);
        Map<?,?> rootMap = (Map<?,?>) root;
        assertEquals(1, rootMap.size());
        Map.Entry<?,?> rootEntry =  rootMap.entrySet().iterator().next();
        assertEquals("Image", rootEntry.getKey());
        Object image = rootEntry.getValue();
        assertInstanceOf(Map.class, image);
        Map<?,?> imageMap = (Map<?,?>) image;
        assertEquals(5, imageMap.size());

        Object value = imageMap.get("Width");
        assertInstanceOf(Integer.class, value);
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_WIDTH), value);

        value = imageMap.get("Height");
        assertInstanceOf(Integer.class, value);
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_HEIGHT), value);

        assertEquals(SAMPLE_SPEC_VALUE_TITLE, imageMap.get("Title"));

        // Another Object, "thumbnail"
        value = imageMap.get("Thumbnail");
        assertInstanceOf(Map.class, value);
        Map<?,?> tnMap = (Map<?,?>) value;
        assertEquals(3, tnMap.size());

        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_HEIGHT), tnMap.get("Height"));
        // for some reason, width is textual, not numeric...
        assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, tnMap.get("Width"));
        assertEquals(SAMPLE_SPEC_VALUE_TN_URL, tnMap.get("Url"));

        // And then number list, "IDs"
        value = imageMap.get("IDs");
        assertInstanceOf(List.class, value);
        List<Object> ids = (List<Object>) value;
        assertEquals(4, ids.size());
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID1), ids.get(0));
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID2), ids.get(1));
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID3), ids.get(2));
        assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID4), ids.get(3));

        // and that's all folks!
    }

    @Test
    public void testUntypedMap() throws Exception
    {
        // to get "untyped" default map-to-map, pass Object.class
        String JSON = "{ \"foo\" : \"bar\", \"crazy\" : true, \"null\" : null }";

        // Not a guaranteed cast theoretically, but will work:
        @SuppressWarnings("unchecked")
        Map<Object,Object> result = (Map<Object,Object>)MAPPER.readValue(JSON, Object.class);
        assertNotNull(result);
        assertInstanceOf(Map.class, result);

        assertEquals(3, result.size());

        assertEquals("bar", result.get("foo"));
        assertEquals(Boolean.TRUE, result.get("crazy"));
        assertNull(result.get("null"));

        // Plus, non existing:
        assertNull(result.get("bar"));
        assertNull(result.get(3));
    }

    @Test
    public void testSimpleVanillaScalars() throws Exception
    {
        assertEquals("foo", OBJECT_READER.readValue(q("foo")));
        assertEquals("foo", OBJECT_READER.withValueToUpdate("xxx").readValue(q("foo")));

        assertEquals(Boolean.FALSE, OBJECT_READER.readValue("false"));
        assertEquals(Boolean.TRUE, OBJECT_READER.readValue(" true "));

        assertEquals(Integer.valueOf(13), OBJECT_READER.readValue("13 "));
        assertEquals(Double.valueOf(0.5), OBJECT_READER.readValue("0.5 "));
    }

    @Test
    public void testSimpleVanillaStructured() throws Exception
    {
        List<?> list = (List<?>) OBJECT_READER.readValue("[ 1, 2, 3]");
        assertEquals(Integer.valueOf(1), list.get(0));
    }

    @Test
    public void testNestedUntyped() throws Exception
    {
        // 05-Apr-2014, tatu: Odd failures if using shared mapper; so work around:
        Object root = OBJECT_READER.readValue(a2q("{'a':3,'b':[1,2], 'c':[3]}"));
        assertInstanceOf(Map.class, root);
        assertEquals(Map.of("a", 3, "b", List.of(1, 2), "c", List.of(3)),
                root);
    }

    @Test
    public void testUntypedWithCustomScalarDesers() throws Exception
    {
        SimpleModule m = new SimpleModule("test-module");
        m.addDeserializer(String.class, new UCStringDeserializer());
        m.addDeserializer(Number.class, new CustomNumberDeserializer(13));
        final ObjectMapper mapper = jsonMapperBuilder()
            .addModule(m)
            .build();

        Object ob = mapper.readValue("{\"a\":\"b\", \"nr\":1 }", Object.class);
        assertInstanceOf(Map.class, ob);
        Object value = ((Map<?,?>) ob).get("a");
        assertNotNull(value);
        assertInstanceOf(String.class, value);
        assertEquals("B", value);

        value = ((Map<?,?>) ob).get("nr");
        assertNotNull(value);
        assertInstanceOf(Number.class, value);
        assertEquals(Integer.valueOf(13), value);
    }

    // Test that exercises non-vanilla variant, with just one simple custom deserializer
    @Test
    public void testNonVanilla() throws Exception
    {
        SimpleModule m = new SimpleModule("test-module");
        m.addDeserializer(String.class, new UCStringDeserializer());
        final ObjectMapper mapper = jsonMapperBuilder()
                .polymorphicTypeValidator(new NoCheckSubTypeValidator())
                .addModule(m)
                .build();
        ObjectReader r = mapper.readerFor(Object.class);
        // Also: since this is now non-vanilla variant, try more alternatives
        List<?> l = (List<?>) r.readValue("[ true, false, 7, 0.5, \"foo\", null]");
        assertEquals(6, l.size());
        assertEquals(Boolean.TRUE, l.get(0));
        assertEquals(Boolean.FALSE, l.get(1));
        assertEquals(Integer.valueOf(7), l.get(2));
        assertEquals(Double.valueOf(0.5), l.get(3));
        assertEquals("FOO", l.get(4));
        assertNull(l.get(5));

        // And Maps
        Map<?,?> map = (Map<?,?>) r.readValue(a2q("{'a':0.25,'b':3,'c':true,'d':false}"));
        assertEquals(Map.of("a", 0.25, "b", 3, "c", true, "d", false), map);

        // And Scalars too; regular and "updating" readers
        l = new ArrayList<>();
        assertEquals(Integer.valueOf(42), r.readValue("42"));
        assertEquals(Integer.valueOf(42), r.withValueToUpdate(l).readValue("42"));
        assertEquals(Double.valueOf(2.5), r.readValue("2.5"));
        assertEquals(Double.valueOf(2.5), r.withValueToUpdate(l).readValue("2.5"));
        // custom String deserializer
        assertEquals("ABC", r.readValue(q("abc")));
        assertEquals("ABC", r.withValueToUpdate(l).readValue(q("abc")));
        assertEquals(true, r.readValue("true"));
        assertEquals(true, r.withValueToUpdate(l).readValue("true"));
        assertEquals(false, r.readValue("false"));
        assertEquals(false, r.withValueToUpdate(l).readValue("false"));
        assertNull(r.readValue("null"));
        assertSame(l, r.withValueToUpdate(l).readValue("null"));
        
        // and minimal nesting
        l = (List<?>) mapper.readValue("[ {}, [] ]", Object.class);
        assertEquals(2, l.size());
        assertEquals(Map.of(), l.get(0));
        assertEquals(List.of(), l.get(1));
    }

    @Test
    public void testUntypedWithListDeser() throws Exception
    {
        SimpleModule m = new SimpleModule("test-module");
        m.addDeserializer(List.class, new ListDeserializer());
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(m)
                .build();
        ObjectReader r = mapper.readerFor(Object.class);
        assertEquals(List.of("X1", "X2", "Xtrue"),
                r.readValue("[1, 2, true]"));

        // And also with alternative. But note! Custom List deserializer NOT
        // used when mapping to Java Arrays
        Object ob2 = r.with(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)
                .readValue("[1, 2, true]");
        assertInstanceOf(Object[].class, ob2);
        assertEquals(List.of(1, 2, true),
                Arrays.asList((Object[]) ob2));
    }

    @Test
    public void testUntypedWithMapDeser() throws Exception
    {
        SimpleModule m = new SimpleModule("test-module");
        m.addDeserializer(Map.class, new YMapDeserializer());
        ObjectMapper mapper = jsonMapperBuilder()
                .addModule(m)
                .build();
        // And then list...
        Object ob = mapper.readValue("{\"a\":true}", Object.class);
        assertInstanceOf(Map.class, ob);
        Map<?,?> map = (Map<?,?>) ob;
        assertEquals(1, map.size());
        assertEquals("Ytrue", map.get("a"));
    }

    @Test
    public void testNestedUntyped989() throws Exception
    {
        DelegatingUntyped pojo;
        ObjectReader r = MAPPER.readerFor(DelegatingUntyped.class);

        pojo = r.readValue("[]");
        assertInstanceOf(List.class, pojo.value);
        pojo = r.readValue("[{}]");
        assertInstanceOf(List.class, pojo.value);

        pojo = r.readValue("{}");
        assertInstanceOf(Map.class, pojo.value);
        pojo = r.readValue("{\"a\":[]}");
        assertInstanceOf(Map.class, pojo.value);
    }

    @Test
    public void testUntypedWithJsonArrays() throws Exception
    {
        // by default we get:
        Object ob = MAPPER.readValue("[1]", Object.class);
        assertInstanceOf(List.class, ob);

        // but can change to produce Object[]:
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)
                .build();
        ob = mapper.readValue("[1, false, true, 0.5, {}]", Object.class);
        assertEquals(Object[].class, ob.getClass());
        assertEquals(List.of(1, false, true, 0.5, Map.of()),
                Arrays.asList((Object[]) ob));
    }

    @Test
    public void testUntypedIntAsLong() throws Exception
    {
        final String JSON = a2q("{'value':3}");
        WrappedUntyped1460 w = MAPPER.readerFor(WrappedUntyped1460.class)
                .readValue(JSON);
        assertEquals(Integer.valueOf(3), w.value);

        w = MAPPER.readerFor(WrappedUntyped1460.class)
                .with(DeserializationFeature.USE_LONG_FOR_INTS)
                .readValue(JSON);
        assertEquals(Long.valueOf(3), w.value);
    }

    // [databind#2115]: Consider `java.io.Serializable` as sort of alias of `java.lang.Object`
    // since all natural target types do implement `Serializable` so assignment works
    @Test
    public void testSerializable() throws Exception
    {
        final String JSON1 = a2q("{ 'value' : 123 }");
        SerContainer cont = MAPPER.readValue(JSON1, SerContainer.class);
        assertEquals(Integer.valueOf(123), cont.value);

        cont = MAPPER.readValue(a2q("{ 'value' : true }"), SerContainer.class);
        assertEquals(Boolean.TRUE, cont.value);

        // But also via Map value, even key
        Map<?,?> map = MAPPER.readValue(JSON1, new TypeReference<Map<String, Serializable>>() { });
        assertEquals(1, map.size());
        assertEquals(Integer.valueOf(123), map.get("value"));

        map = MAPPER.readValue(JSON1, new TypeReference<Map<Serializable, Object>>() { });
        assertEquals(1, map.size());
        assertEquals("value", map.keySet().iterator().next());
    }

    /*
    /**********************************************************
    /* Test methods, merging
    /**********************************************************
     */

    @Test
    public void testValueUpdateVanillaUntyped() throws Exception
    {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("a", 42);

        // First update Map with JSON Object
        ObjectReader r = MAPPER.readerFor(Object.class).withValueToUpdate(map);
        Object result;

        result = r.readValue(a2q("{'b': 0.25, 'c': [] }"));
        assertSame(map, result);
        assertEquals(3, map.size());
        assertEquals(0.25, map.get("b"));
        assertEquals(List.of(), map.get("c"));

        // Then List with Array
        List<Object> list = new ArrayList<>();
        list.add(1);
        r = MAPPER.readerFor(Object.class).withValueToUpdate(list);
        result = r.readValue("[ true, -0.5, { } ]");
        assertSame(list, result);
        assertEquals(List.of(1, true, -0.5, Map.of()), result);

        // Then mismatches: Map with JSON Array
        r = MAPPER.readerFor(Object.class)
                .withValueToUpdate(map);
        result = r.readValue("[ 42, -0.25, false, null ]");
        List<Object> exp = new ArrayList<>();
        exp.add(42);
        exp.add(-0.25);
        exp.add(false);
        exp.add(null);
        assertEquals(exp, result);

        // And then List with JSON Object
        r = MAPPER.readerFor(Object.class)
                .withValueToUpdate(new ArrayList<>());
        map.clear();
        map.put("a", 0.5);
        map.put("b", null);
        result = r.readValue(a2q("{'a': 0.5, 'b': null}"));
        assertEquals(map, result);
    }

    @Test
    public void testValueUpdateCustomUntyped() throws Exception
    {
        SimpleModule m = new SimpleModule("test-module")
                .addDeserializer(String.class, new UCStringDeserializer());
        final ObjectMapper customMapper = jsonMapperBuilder()
                .addModule(m)
                .build();

        Map<String, Object> map = new LinkedHashMap<>();
        map.put("a", 42);

        ObjectReader r = customMapper.readerFor(Object.class).withValueToUpdate(map);
        Object result = r.readValue(a2q("{'b': 'value', 'c': 111222333444, 'enabled': true}"));
        assertSame(map, result);
        assertEquals(4, map.size());
        assertEquals("VALUE", map.get("b"));
        assertEquals(Long.valueOf(111222333444L), map.get("c"));
        assertEquals(Boolean.TRUE, map.get("enabled"));

        // Try same with other types, too
        List<Object> list = new ArrayList<>();
        list.add(1);
        r = customMapper.readerFor(Object.class).withValueToUpdate(list);
        result = r.readValue(a2q("[ 2, 'foobar' ]"));
        assertSame(list, result);
        assertEquals(3, list.size());
        assertEquals("FOOBAR", list.get(2));
    }

    @Test
    public void testUntypedCustomMapWithDups() throws Exception
    {
        // Important: needs something non-vanilla to trigger different
        // code path!
        SimpleModule m = new SimpleModule("test-module")
                .addDeserializer(String.class, new UCStringDeserializer());
        final ObjectMapper customMapper = jsonMapperBuilder()
                .addModule(m)
                .build();
        ObjectReader r = customMapper.readerFor(Object.class);
        assertEquals(Map.of("a", 0), r.readValue(a2q("{'a': false, 'a': 0}")));
        assertEquals(Map.of("a", 1, "b", false),
                r.readValue(a2q("{'a':0, 'b': false, 'a': 1}")));

        Object ob = r.readValue(a2q("""
                { 'a': 1, 'b': true, 'c': 0.25, 'a': 'abc', 'a':2, 'b': 3 }
                """
              ));
        assertEquals(Map.of("a", 2, "b", 3, "c", 0.25), ob);
    }

    /*
    /**********************************************************
    /* Test methods, polymorphic
    /**********************************************************
     */

    // Allow 'upgrade' of big integers into Long, BigInteger
    @Test
    public void testObjectSerializeWithLong() throws Exception
    {
        final ObjectMapper mapper = jsonMapperBuilder()
                .activateDefaultTyping(NoCheckSubTypeValidator.instance,
                        DefaultTyping.JAVA_LANG_OBJECT, As.PROPERTY)
                .build();
        final long VALUE = 1337800584532L;

        String serialized = "{\"timestamp\":"+VALUE+"}";
        // works fine as node
        JsonNode deserialized = mapper.readTree(serialized);
        assertEquals(VALUE, deserialized.get("timestamp").asLong());
        // and actually should work for Maps too
        Map<?,?> deserMap = mapper.readValue(serialized, Map.class);
        Number n = (Number) deserMap.get("timestamp");
        assertNotNull(n);
        assertSame(Long.class, n.getClass());
        assertEquals(Long.valueOf(VALUE), n);
    }

    @Test
    public void testPolymorphicUntypedVanilla() throws Exception
    {
        ObjectReader rDefault = jsonMapperBuilder()
                .polymorphicTypeValidator(new NoCheckSubTypeValidator())
                .build()
                .readerFor(WrappedPolymorphicUntyped.class);
        ObjectReader rAlt = rDefault
                .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS,
                        DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
        WrappedPolymorphicUntyped w;

        w = rDefault.readValue(a2q("{'value':10}"));
        assertEquals(Integer.valueOf(10), w.value);
        w = rAlt.readValue(a2q("{'value':10}"));
        assertEquals(BigInteger.TEN, w.value);

        w = rDefault.readValue(a2q("{'value':5.0}"));
        assertEquals(Double.valueOf(5.0), w.value);
        w = rAlt.readValue(a2q("{'value':5.0}"));
        assertEquals(new BigDecimal("5.0"), w.value);

        StringBuilder sb = new StringBuilder(100).append("[0");
        for (int i = 1; i < 100; ++i) {
            sb.append(", ").append(i);
        }
        sb.append("]");
        final String INT_ARRAY_JSON = sb.toString();

        // First read as-is, no type wrapping
        Object ob = MAPPER.readerFor(Object.class)
                .with(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)
                .readValue(INT_ARRAY_JSON);
        assertInstanceOf(Object[].class, ob);
        Object[] obs = (Object[]) ob;
        for (int i = 0; i < 100; ++i) {
            assertEquals(Integer.valueOf(i), obs[i]);
        }

        // Finally, true polymorphism
        w = rDefault.readValue(a2q("{'value': ['java.util.Date', 123]}"));
        assertThat(w.value).isInstanceOf(java.util.Date.class);
    }

    @Test
    public void testPolymorphicUntypedCustom() throws Exception
    {
        // register module just to override one deserializer, to prevent use of Vanilla deser
        SimpleModule m = new SimpleModule("test-module")
                .addDeserializer(String.class, new UCStringDeserializer());
        final ObjectMapper customMapper = jsonMapperBuilder()
                .addModule(m)
                .polymorphicTypeValidator(new NoCheckSubTypeValidator())
                .build();
        ObjectReader rDefault = customMapper.readerFor(WrappedPolymorphicUntyped.class);

        WrappedPolymorphicUntyped w = rDefault.readValue(a2q("{'value':10}"));
        assertEquals(Integer.valueOf(10), w.value);

        w = rDefault.readValue(a2q("{'value':9988776655}"));
        assertEquals(Long.valueOf(9988776655L), w.value);
        w = rDefault.readValue(a2q("{'value':0.75}"));
        assertEquals(Double.valueOf(0.75), w.value);

        w = rDefault.readValue(a2q("{'value':'abc'}"));
        assertEquals("ABC", w.value);
        w = rDefault.readValue(a2q("{'value':false}"));
        assertEquals(Boolean.FALSE, w.value);
        w = rDefault.readValue(a2q("{'value':null}"));
        assertNull(w.value);

        // but... actually how about real type id?
        final Object SHORT = Short.valueOf((short) 3);
        String json = customMapper.writeValueAsString(new WrappedPolymorphicUntyped(SHORT));

        WrappedPolymorphicUntyped result = rDefault.readValue(json);
        assertEquals(SHORT, result.value);
    }

    /*
    /**********************************************************
    /* Test methods, additional coverage
    /**********************************************************
     */

    @Test
    public void testEmptyArrayAndObject() throws Exception
    {
        assertEquals(List.of(), MAPPER.readValue("[]", Object.class));
        assertEquals(Map.of(), MAPPER.readValue("{}", Object.class));
    }

    @Test
    public void testSingleElementArrayAndObject() throws Exception
    {
        assertEquals(List.of(42), MAPPER.readValue("[42]", Object.class));
        assertEquals(Map.of("key", true),
                MAPPER.readValue("{\"key\": true}", Object.class));
    }

    @Test
    public void testFloatDeserializationWithBigDecimal() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
                .build();

        Object result = mapper.readValue("3.14159", Object.class);
        assertNotNull(result);
        assertInstanceOf(BigDecimal.class, result);
        assertEquals(new BigDecimal("3.14159"), result);
    }

    @Test
    public void testFloatTypesFloat32AndFloat64() throws Exception
    {
        Object result = MAPPER.readValue("1.5", Object.class);
        assertInstanceOf(Double.class, result);
        result = MAPPER.readValue("2.718281828", Object.class);
        assertInstanceOf(Double.class, result);
    }

    @Test
    public void testNaNHandling() throws Exception
    {
        // NaN values should be handled specially
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
                .build();

        Object result = mapper.readValue("NaN", Object.class);
        assertInstanceOf(Double.class, result);
        assertTrue(((Double) result).isNaN());
    }

    @Test
    public void testNullInDifferentContexts() throws Exception
    {
        // Null as root value
        Object result = MAPPER.readValue("null", Object.class);
        assertNull(result);

        // Null in array
        List<?> list = (List<?>) MAPPER.readValue("[null, 1, null]", Object.class);
        assertEquals(3, list.size());
        assertNull(list.get(0));
        assertEquals(Integer.valueOf(1), list.get(1));
        assertNull(list.get(2));

        // Null in object
        Map<?,?> map = (Map<?,?>) MAPPER.readValue("{\"a\":null,\"b\":2}", Object.class);
        assertEquals(2, map.size());
        assertTrue(map.containsKey("a"));
        assertNull(map.get("a"));
        assertEquals(Integer.valueOf(2), map.get("b"));
    }

    @Test
    public void testBigIntegerCoercion() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)
                .build();

        Object result = mapper.readValue("12345678901234567890", Object.class);
        assertNotNull(result);
        assertInstanceOf(BigInteger.class, result);
        assertEquals(new BigInteger("12345678901234567890"), result);
    }

    @Test
    public void testLargeArray() throws Exception
    {
        // Test array with more than 2 elements to trigger ObjectBuffer usage
        StringBuilder json = new StringBuilder("[");
        for (int i = 0; i < 100; i++) {
            if (i > 0) json.append(",");
            json.append(i);
        }
        json.append("]");

        List<?> result = (List<?>) MAPPER.readValue(json.toString(), Object.class);
        assertEquals(100, result.size());
        assertEquals(Integer.valueOf(0), result.get(0));
        assertEquals(Integer.valueOf(99), result.get(99));
    }

    @Test
    public void testLargeObject() throws Exception
    {
        // Test object with more than 2 properties
        StringBuilder json = new StringBuilder("{");
        for (int i = 0; i < 50; i++) {
            if (i > 0) json.append(",");
            json.append("\"key").append(i).append("\":").append(i);
        }
        json.append("}");

        Map<?,?> result = (Map<?,?>) MAPPER.readValue(json.toString(), Object.class);
        assertEquals(50, result.size());
        assertEquals(Integer.valueOf(0), result.get("key0"));
        assertEquals(Integer.valueOf(49), result.get("key49"));
    }

    @Test
    public void testMixedTypesInArray() throws Exception
    {
        String json = "[1, \"text\", true, null, 3.14, {\"nested\":\"object\"}, [1,2,3]]";
        List<?> result = (List<?>) MAPPER.readValue(json, Object.class);

        assertEquals(7, result.size());
        assertEquals(Integer.valueOf(1), result.get(0));
        assertEquals("text", result.get(1));
        assertEquals(Boolean.TRUE, result.get(2));
        assertNull(result.get(3));
        assertEquals(Double.valueOf(3.14), result.get(4));
        assertInstanceOf(Map.class, result.get(5));
        assertInstanceOf(List.class, result.get(6));
    }

    @Test
    public void testDeeplyNestedStructures() throws Exception
    {
        String json = "{\"level1\":{\"level2\":{\"level3\":{\"level4\":{\"value\":\"deep\"}}}}}";
        Map<?,?> result = (Map<?,?>) MAPPER.readValue(json, Object.class);

        Map<?,?> level1 = (Map<?,?>) result.get("level1");
        Map<?,?> level2 = (Map<?,?>) level1.get("level2");
        Map<?,?> level3 = (Map<?,?>) level2.get("level3");
        Map<?,?> level4 = (Map<?,?>) level3.get("level4");
        assertEquals("deep", level4.get("value"));
    }

    @Test
    public void testObjectWithArraysAndObjects() throws Exception
    {
        String json = "{\"numbers\":[1,2,3],\"nested\":{\"flag\":true},\"text\":\"hello\"}";
        Map<?,?> result = (Map<?,?>) MAPPER.readValue(json, Object.class);

        assertEquals(3, result.size());
        List<?> numbers = (List<?>) result.get("numbers");
        assertEquals(3, numbers.size());

        Map<?,?> nested = (Map<?,?>) result.get("nested");
        assertEquals(Boolean.TRUE, nested.get("flag"));

        assertEquals("hello", result.get("text"));
    }
}