You need to use Object as Map value because it could be another Map, List or primitive (String, Integer, etc.). Jackson allows also to manipulate JSON using JsonNode types. We need to traverse JSON object but also JSON array (you forgot about it). In that case we need to:

  • Deserialise JSON to JsonNode.
  • Traverse it using JsonNode API.
  • Convert to the Map

Simple implementation:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        String json = "{\"Key1\": \"value1\", \"Key2\": { \"Key2.1\": \"value2.1\" }, \"Key3\":[{\"pRiMe\":11}]}";
        Map<String, Object> map = convertJsonStringToMap(json);

        System.out.println(map);
    }

    private static Map<String, Object> convertJsonStringToMap(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(json);
        transformKeys(node);

        TypeReference mapType = new TypeReference<Map<String, Object>>() {
        };

        return mapper.convertValue(node, mapType);
    }

    private static void transformKeys(JsonNode parent) {
        if (parent.isObject()) {
            ObjectNode node = (ObjectNode) parent;

            List<String> names = new ArrayList<>();
            node.fieldNames().forEachRemaining(names::add);

            names.forEach(name -> {
                JsonNode item = node.remove(name);
                transformKeys(item);
                node.replace(name.toLowerCase(), item);
            });
        } else if (parent.isArray()) {
            ArrayNode array = (ArrayNode) parent;
            array.elements().forEachRemaining(JsonApp::transformKeys);
        }
    }
}

Above code prints:

{key1=value1, key2={key2.1=value2.1}, key3=[{prime=11}]}

We got rid of unsafe casting and our implementation is more concise.

Answer from Michał Ziober on Stack Overflow
🌐
Tabnine
tabnine.com › home › code library
Code Library - Tabnine
July 25, 2024 - Get the answers and suggestions you need from our AI code assistant. Get started in minutes with a free 90 day trial of Tabnine Pro.
Top answer
1 of 1
4

You need to use Object as Map value because it could be another Map, List or primitive (String, Integer, etc.). Jackson allows also to manipulate JSON using JsonNode types. We need to traverse JSON object but also JSON array (you forgot about it). In that case we need to:

  • Deserialise JSON to JsonNode.
  • Traverse it using JsonNode API.
  • Convert to the Map

Simple implementation:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        String json = "{\"Key1\": \"value1\", \"Key2\": { \"Key2.1\": \"value2.1\" }, \"Key3\":[{\"pRiMe\":11}]}";
        Map<String, Object> map = convertJsonStringToMap(json);

        System.out.println(map);
    }

    private static Map<String, Object> convertJsonStringToMap(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(json);
        transformKeys(node);

        TypeReference mapType = new TypeReference<Map<String, Object>>() {
        };

        return mapper.convertValue(node, mapType);
    }

    private static void transformKeys(JsonNode parent) {
        if (parent.isObject()) {
            ObjectNode node = (ObjectNode) parent;

            List<String> names = new ArrayList<>();
            node.fieldNames().forEachRemaining(names::add);

            names.forEach(name -> {
                JsonNode item = node.remove(name);
                transformKeys(item);
                node.replace(name.toLowerCase(), item);
            });
        } else if (parent.isArray()) {
            ArrayNode array = (ArrayNode) parent;
            array.elements().forEachRemaining(JsonApp::transformKeys);
        }
    }
}

Above code prints:

{key1=value1, key2={key2.1=value2.1}, key3=[{prime=11}]}

We got rid of unsafe casting and our implementation is more concise.

🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 3151
`ObjectMapper.convertValue()` does not work with `@JsonTypeInfo` · Issue #3151 · FasterXML/jackson-databind
May 13, 2021 - Describe the bug When using ObjectMapper to serialize and deserialize an object that contains JsonTypeInfo a MismatchedInputException is produced: java.lang.IllegalArgumentException: Unexpected token (VALUE_STRING), expected START_OBJECT...
Author: FasterXML
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 450
CollectionSerializer gets confused on how to handle elements · Issue #450 · FasterXML/jackson-databind
April 29, 2014 - import static org.junit.Assert.*; ... StdConverter<Inner, List> { @Override @SuppressWarnings("unchecked") public List convert(Inner value) { List list = new LinkedList(); Map map = new ObjectMapper().convertValue(value, Map.class); for(int ...
Author: FasterXML
🌐
Northcoder
northcoder.com › post › jackson-object-mapper-which-way-is
Jackson's ObjectMapper and TypeReference | northCoder
January 15, 2021 - Some notes on Jackson’s ObjectMapper, when using it without any custom POJO classes, to deserialize an arbitrary piece of JSON to a Java Map.
🌐
Java Tips
javatips.net › api › com.fasterxml.jackson.databind.objectmapper
Java Examples for com.fasterxml.jackson.databind.ObjectMapper
public static byte[] convertObjectToFormUrlEncodedBytes(Object object) { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); @SuppressWarnings("unchecked") Map<String, Object> propertyValues = mapper.convertValue(object, Map.class); Set<String> ...
Top answer
1 of 2
40

The reason what you've shown is unsafe is that with this assignment:

Class<T> type = typeMap.get(key);

T does not need to have anything to do with the Class retrieved from the map. T is always inferred from the surrounding context of the call to get. For example I can do this sequence of calls:

// T is inferred from the arguments as String (which is fine)
example.put("k", "v");
// T is inferred from the return value target type as Integer
Integer i = example.get("k");

Inside the get method, String.class is correctly retrieved from the type map, but an unchecked conversion is made to Class<Integer>. The call to type.cast(...) doesn't throw, because the value retrieved from the data map is a String. An implicit checked cast then actually happens to the return value, casting it to Integer and a ClassCastException is thrown.

This strange interaction is due to type erasure.


So, when we are storing multiple types in a single data structure, there are a number of ways to approach it, depending on what our needs are.

1. We can forego compilation checks if there is no way to perform them.

Storing the Class is pointless for the most part here because, as I showed above, it doesn't perform a useful validation. So we could redesign the map along the following lines:

class Example {
    private final Map<String, Object> m = new HashMap<>();

    void put(String k, Object v) {
        m.put(k, v);
    }

    Object getExplicit(String k) {
        return m.get(k);
    }

    @SuppressWarnings("unchecked")
    <T> T getImplicit(String k) {
        return (T) m.get(k);
    }
}

getExplicit and getImplicit do a similar thing but:

String a = (String) example.getExplicit("k");
// the generic version allows an implicit cast to be made
// (this is essentially what you're already doing)
String b = example.getImplicit("k");

In both cases we're just relying on our own awareness as a programmer to not make mistakes.

Suppressing warnings isn't necessarily bad, it's just important to understand what they actually mean and what the implications are.

2. Pass a Class to get so the return value must be valid.

This is the way I've seen it done typically.

class Example {
    private final Map<String, Object> m = new HashMap<>();

    void put(String k, Object v) {
        m.put(k, v);
    }

    <T> T get(String k, Class<T> c) {
        Object v = m.get(k);
        return c.isInstance(v) ? c.cast(v) : null;
    }
}

example.put("k", "v");
// returns "v"
String s = example.get("k", String.class);
// returns null
Double d = example.get("k", Double.class);

But, of course, it means we need to pass two parameters to get.

3. Parameterize the keys.

This is a novel but more advanced and it may or may not be more convenient.

class Example {
    private final Map<Key<?>, Object> m = new HashMap<>();

    <V> Key<V> put(String s, V v) {
        Key<V> k = new Key<>(s, v);
        put(k, v);
        return k;
    }

    <V> void put(Key<V> k, V v) {
        m.put(k, v);
    }

    <V> V get(Key<V> k) {
        Object v = m.get(k);
        return k.c.isInstance(v) ? k.c.cast(v) : null;
    }

    static final class Key<V> {
        private final String k;
        private final Class<? extends V> c;

        @SuppressWarnings("unchecked")
        Key(String k, V v) {
            // this cast will always be safe unless
            // the outside world is doing something fishy
            // like using raw types
            this(k, (Class<? extends V>) v.getClass());
        }

        Key(String k, Class<? extends V> c) {
            this.k = k;
            this.c = c;
        }

        @Override
        public int hashCode() {
            return k.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            return (o instanceof Key<?>) && ((Key<?>) o).k.equals(k);
        }
    }
}

So e.g.:

Key<Float> k = example.put("k", 1.0f);
// returns 1.0f
Float f = example.get(k);
// returns null
Double d = example.get(new Key<>("k", Double.class));

This might make sense if the entries are known or predictable so we can have something like:

final class Keys {
    private Keys() {}
    static final Key<Foo> FOO = new Key<>("foo", Foo.class);
    static final Key<Bar> BAR = new Key<>("bar", Bar.class);
}

Then we don't have to construct a key object any time a retrieval is done. This works very well especially for adding some strong typing to stringly-typed scenarios.

Foo foo = example.get(Keys.FOO);

4. Don't have a map any kind of object can be put in, use polymorphism of some kind.

When possible and not too cumbersome, it's a good option. If there is a common behavior that the different types are used for, make it an interface or superclass so we don't have to use casting.

A simple example might be like this:

// bunch of stuff
Map<String, Object> map = ...;

// store some data
map.put("abc", 123L);
map.put("def", 456D);

// wait awhile
awhile();

// some time later, consume the data
// being particular about types
consumeLong((Long) map.remove("abc"));
consumeDouble((Double) map.remove("def"));

And we could instead substitute something like this:

Map<String, Runnable> map = ...;

// store operations as well as data
// while we know what the types are
map.put("abc", () -> consumeLong(123L));
map.put("def", () -> consumeDouble(456D));

awhile();

// consume, but no longer particular about types
map.remove("abc").run();
map.remove("def").run();
2 of 2
4

You are attempting to assign an element of type Class to a variable of type Class<T>. Of course that's an unchecked assignment. You seem to be implementing a heterogeneous map. Java (and any other strongly-typed language) has no way to express the value type of your map in a statically-type-safe way.

That is because the element types are only known at runtime. Expecting the compiler to statically type-check things that are not yet known is asking too much of it. The compiler can't even do reasonable static type inference, so expecting it to predict the future for dynamic type inference is really a stretch.

🌐
Mkyong
mkyong.com › home › java › java – convert object to map example
Java – Convert Object to Map example | mkyong.com
October 29, 2016 - Note that it is better to use TypeReference instead of a direct class in this case as the second rgument to convertValue(). This way we have a checked assignment to correctly typed map instead of Map.
Find elsewhere
🌐
Baeldung
baeldung.com › home › java › core java › java warning “unchecked conversion”
Java Warning “unchecked conversion” | Baeldung
June 21, 2025 - The warning message “unchecked conversion” implies that we should check the conversion before the assignment.
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 4661
ObjectMapper convertValue does not check if the value is already of target type · Issue #4661 · FasterXML/jackson-databind
August 13, 2024 - Why does objectMapper.convertValue not check if the object already has the desired target type and then return the object immediatly. jackson-databind/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java Line 4602 in 5e0deda //...
Author: FasterXML
🌐
Couchbase
couchbase.com › mobile › couchbase lite
Crash with attachment after upgrading from CBL 1.4 to 2.6 - Couchbase Lite - Couchbase Forums
October 17, 2019 - Android 9 CBL 2.6 App in production used CBL 1.4. Now an update rolled out to beta users with CBL 2.6 and a when parsing a document with attachment a crash occurs. The document is: { "barcodes": [], "category": { "id": "feacf1232d19", "name": "épicerie" }, "channels": "CacoWfYv9STcbFGuCyf...
🌐
Oracle
docs.oracle.com › javase › specs › jls › se16 › html › jls-5.html
Chapter 5. Conversions and Contexts
March 16, 2026 - If, after the conversions listed for an invocation context have been applied, the resulting type is a raw type (§4.8), an unchecked conversion (§5.1.9) may then be applied. A value of the null type (the null reference is the only such value) may be assigned to any reference type.
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 3586
ObjectMapper.convertValue() should throw JsonProcessingException · Issue #3586 · FasterXML/jackson-databind
August 26, 2022 - The same applies to convertValue(). I think convertValue catches JsonProcessingException by accident: } catch (IOException e) { // should not occur, no real i/o... throw new IllegalArgumentException(e.getMessage(), e); } JsonProcessingException is not an i/o problem, but still a sub class of IOException. No one assigned ·
Author: FasterXML
🌐
Medium
medium.com › @random_developer › objectmapper-in-java-7578a15cc065
ObjectMapper in Java. In enterprise Java applications, you… | by Random developer | Medium
June 16, 2024 - ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model (JsonNode), as well as related functionality for performing conversions.
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 779
Consider adding `UncheckedObjectReader`/`-Writer` (or `Safe-`) · Issue #779 · FasterXML/jackson-databind
May 3, 2015 - This is a half-baked idea at this point, so feel free to add comments for improvements, or for/against the idea. Note however that API definition of ObjectMapper itself is not under discussion for Jackson 2.x, at least; and even for 3.x change to unchecked exceptions would be a major change and would need separate discussion.
Author: FasterXML