Skip to content

ObjectMapper.readValue("123", Void.TYPE) throws "should never occur" #2679

@stolsvik

Description

@stolsvik

objectMapper.readValue("123", Void.class) returns null.
objectMapper.readValue("123", Void.TYPE) throws JsonMappingException, with cause IllegalArgumentException, whose code comment reads "should never occur".
objectMapper.readValue("123", void.class) also throws, since void.class == Void.TYPE

I would argue that Void.TYPE and void.class should also return null, unconditionally.

.. basically, in the same vein as if specifying Void.class, you get null, and considering
objectMapper.readValue("123", Integer.TYPE) returns 123 - and that the only value a Void variable can have is null.

Cause Exception:

Caused by: java.lang.IllegalArgumentException: Internal error: can't find deserializer for void
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers.find(NumberDeserializers.java:109)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findDefaultDeserializer(BasicDeserializerFactory.java:1845)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.findStdDeserializer(BeanDeserializerFactory.java:167)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:131)
...

The reason is that this NumberDeserializers first checks whether the class is primitive (yes), and then has checks for all of Integer.TYPE, Boolean.TYPE etc, but lacks one for Void.TYPE. It thus falls out of that "then" block, and hits the default "should never occur".

The reason why this hits me, is that I have a (de)serialization situation in my library where users must specify which type they need their incoming message as - but can also effectively say "I don't care". Up until now I've used Void.class for this, but have come to realize that this is actually not correct - as the Void class that holds the TYPE field is "just a random class". You want either void.class (lowercase 'v'), or "Void.TYPE" - these two are actually the same instance, as opposed to Void.class.

The Void.class actually works because there is special handling for this case in JdkDeserializers:

            if (rawType == Void.class) {
                return NullifyingDeserializer.instance;
            }

This does not catch the void.class (or Void.TYPE), and it is anyway too late, since the NumberDeserializers class already has crashed it.

Here's a small test:

public class TestVoid {
    public static void main(String[] args) throws JsonProcessingException {
        System.out.println("Void.class: "+System.identityHashCode(Void.class));
        System.out.println("void.class: "+System.identityHashCode(void.class));
        System.out.println("Void.TYPE: "+System.identityHashCode(Void.TYPE));
        System.out.println("Void.TYPE == void.class: "+(void.class == Void.TYPE));
        System.out.println("----");

        ObjectMapper ser = new ObjectMapper();
        System.out.println("Deserialized Void.class: "+ser.readValue("123", Void.class));
        System.out.println("Deserialized Integer.TYPE: "+ser.readValue("123", Integer.TYPE));
        System.out.println("Deserialized void.class: "+ser.readValue("123", void.class));
        System.out.println("Deserialized Void.TYPE: "+ser.readValue("123", Void.TYPE));
    }
}

Prints something like:

Void.class: 999966131
void.class: 1989780873
Void.TYPE: 1989780873
Void.TYPE == void.class: true
----
Deserialized Void.class: null
Deserialized Integer.TYPE: 123
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Internal error: can't find deserializer for void
 at [Source: (String)"123"; line: 1, column: 1]

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