FasterXML / jackson-module-jsonSchema

Module for generating JSON Schema (v3) definitions from POJOs
370 stars 136 forks source link

Deserialize empty JSON schemas #110

Closed quilicicf closed 7 years ago

quilicicf commented 8 years ago

Affects: 2.8.2

Issue

Spec pointer

If the property is not defined or is not in this list, then any type of value is acceptable.

I'd expect an empty JSON schema to be deserialized as AnySchema.

How to reproduce

POM file

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>fr.quilicicf.test</groupId>
    <artifactId>jackson-module-jsonSchema</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-jsonSchema</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

Java file

package fr.quilicicf.test;

import org.junit.Assert;
import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.types.AnySchema;

public class JsonSchemaDeserialization {

    @Test
    public void test() throws Exception {
        JsonSchema jsonSchema = new ObjectMapper().readValue("{}", JsonSchema.class);
        Assert.assertTrue(jsonSchema instanceof AnySchema);
    }
}

Trace

com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id  (for class com.fasterxml.jackson.module.jsonSchema.JsonSchema)
 at [Source: {}; line: 1, column: 2]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:261)
    at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:1340)
    at com.fasterxml.jackson.databind.DeserializationContext.reportWrongTokenException(DeserializationContext.java:1196)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:157)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:142)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)
    at fr.quilicicf.test.JsonSchemaDeserialization.test(JsonSchemaDeserialization.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
cowtowncoder commented 8 years ago

Sounds reasonable for specific case of empty Object, but would this cause problems for other kinds of invalid schema JSON? Default implementation is used in every case where type identifier is missing. Perhaps this is not a big problem (any JSON properties found would likely not have counterpart in AnySchema.

quilicicf commented 8 years ago

JSON schemas without type being of type any was my interpretation of the spec. Maybe I'm wrong. I'll look at the failing tests.

quilicicf commented 8 years ago

Looks like one cannot deserialize directly in any subtype of JsonSchema anymore, only in JsonSchema itself.

When trying to MAPPER.readValue(str, ObjectSchema.class) it says

AnySchema is not a subtype of ObjectSchema.

I'm wondering if defaultImpl should be applied to subclasses of JsonSchema but I don't know Jackson enough to make a clean fix on that issue I'm afraid. Any suggestion ?

cowtowncoder commented 8 years ago

@quilicicf Right, you can not ask for ObjectSchema, and then be handed AnySchema since latter is not a subtype of former -- you can not cast AnySchema into ObjectSchema. This is... a problematic case, and ideally I think there should be a way to try to handle this. But as things are, I am not sure how that could be achieved.

Ideally, I think, it should be possible to specify defaultImpl that obeys contract at @JsonTypeInfo (true here), but also request a subtype, and only fail if there is an actual problem (that is, attempt was made to use invalid defauilt implementation) -- but work if type actually does match. Problem comes from the fact that code at low level only sees the potential problem, of base type not the one where @JsonTypeInfo was declared, but one that user specified (ObjectSchema), and correctly (wrt information it has) determines that two are not compatible.

I'll have to think about this, but it is probably safe to say that fix, if one is found, will not be found on short term.

quilicicf commented 8 years ago

OK for me. The fix I did works for us because we always deserialize into class JsonSchema so nothing urgent on our side, we forked the module.

If you find a complete fix, we'd be glad to switch back to the official implementation though.

Thanks for your time.

quilicicf commented 8 years ago

And as I'm being told, we'd like to get rid of that fork ASAP.