FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.51k stars 1.37k forks source link

No way to both specify type ID and pass by reference #1641

Open ryegleason opened 7 years ago

ryegleason commented 7 years ago

I'm using 2.9.0.pr3 for all libraries, and the java code looks like this:

YAMLMapper mapper = new YAMLMapper(new YAMLFactory());
mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));

TaskDoer doer = mapper.readValue(new File("in.yml"), TaskDoer.class);

class TaskDoer{
    private ImportantTask task;
    private Runner runner;

    @JsonCreator
    public TaskDoer(ImportantTask task, Runner runner) {
        this.task = task;
        this.runner = runner;
    }
}

@JsonIdentityInfo(generator=ObjectIdGenerators.StringIdGenerator.class)
class ImportantTask implements Runnable{
    private String name;

    @JsonCreator
    public ImportantTask(String name) {
        this.name = name;
    }
}

interface Runnable{}

@JsonIdentityInfo(generator=ObjectIdGenerators.StringIdGenerator.class)
class Runner {
    private Runnable toRun;

    @JsonCreator
    public Runner(Runnable toRun) {
        this.toRun = toRun;
    }
}

in.yml is

---
task:
    '@id': task
    name: "Important thing"
runner:
    '@id': runner
    toRun:
        *task

and it throws an error of

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Can not construct instance of Runnable (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (File); line: 8, column: 9] (through reference chain: TaskDoer["runner"]->Runner["toRun"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1407)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1005)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:163)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:519)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:527)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:416)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1266)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:325)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1236)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:157)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:519)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:527)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:416)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1266)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:325)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3999)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2878)
    at JacksonTester.main(JacksonTester.java:19)

This should, in theory, be able to work because all the type info is already there, in the referenced object that has already been constructed (should I create a separate FEATURE issue for this?). In practice, I know you have to specify the type info or default implementation. However, there is seemingly no way to specify type info. I annotated Runnable with @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class") and changed in.yml to

---
task:
    '@class': ImportantTask
    '@id': task
    name: "Important thing"
runner:
    '@id': runner
    toRun:
        '@class': ImportantTask
        *task

and got a SnakeYaml error because it couldn't parse "task". When the order of task and '@class': ImportantTask are flipped, the error is instead

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class Runnable]: missing type id property '@class' (for POJO property 'toRun')
 at [Source: (File); line: 9, column: 9] (through reference chain: TaskDoer["runner"]->Runner["toRun"])
    at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
    at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1584)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1195)
    at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:300)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:88)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:152)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:517)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:527)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:416)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1266)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:325)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithObjectId(BeanDeserializerBase.java:1236)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:157)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:519)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:527)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:416)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1266)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:325)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3999)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2878)
    at JacksonTester.main(JacksonTester.java:16)
cowtowncoder commented 7 years ago

I think the first question is whether this would work with JSON or not: if yes, issue belongs here and is something general. If not, it is likely due to something related to use of "native" type and/or object ids; something that YAML has and JSON does not (and instead has to use generic properties, no data/metadata separation).

Combination should work, and I think there are JSON tests that support this, so I would guess it is more likely to be YAML specific. But would be good to verify: if it occurs with JSON, reproduction and fixing should be easier.

cowtowncoder commented 7 years ago

Either json-specific reproduction is needed here; or (more likely?), should be re-created under jackson-dataformats-text for YAML (https://github.com/FasterXML/jackson-dataformats-text/issues).

ryegleason commented 7 years ago

Okay, I'll do that. For now and to help any future people who stumble upon this issue, the workaround is to use @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "@class") instead of @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class"), property doesn't seem to work but WRAPPER_OBJECT works great.