Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic deserialization doesn't support multiple levels of inheritance. #1188

Closed
clin88 opened this issue Apr 7, 2016 · 5 comments
Closed

Comments

@clin88
Copy link

clin88 commented Apr 7, 2016

Say I have a

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = AndRule.class, name = "$and"),
    @JsonSubTypes.Type(value = OrRule.class, name = "$or"),
    @JsonSubTypes.Type(value = ConstraintRule.class, name = "$constraint")
})
public interface Rule {
    boolean evaluate(ExperimentContext context);
}

Where ConstraintRule is an abstract class that looks like:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.CUSTOM,
    include = JsonTypeInfo.As.PROPERTY,
    property = "operator"
)
@JsonTypeIdResolver(ConstraintRule.ConstraintRuleIdResolver.class)
public abstract class ConstraintRule<T> implements Rule {

    public static class ConstraintRuleIdResolver extends TypeIdResolverBase {
        ...
        @Override
        public JavaType typeFromId(DatabindContext context, String id) {
            switch (id) {
                case "$in": case "$nin":
                    return context.constructType(CollectionRule.class);
                default:
                    return context.constructType(ComparableRule.class);
            }
        }
    }

    public static class CollectionRule<T extends Collection> extends ConstraintRule<T> {
        @Override
        public boolean evaluate(ExperimentContext context) { ... }
    }

    public static class ComparableRule<T extends Comparable> extends ConstraintRule<T> {
        @Override
        public boolean evaluate(ExperimentContext context) { ... }
    }
}

and data that embeds a CollectionRule (one of the polymorphic types of the abstract ConstraintRule) inside of another Rule type.

Results in this error from AbstractDeserializer:

        throw ctxt.instantiationException(_baseType.getRawClass(),
                "abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information");

I think this is because TypeDeserializerBase doesn't try to find a root value deserializer in _findDeserializer:

    protected final JsonDeserializer<Object> _findDeserializer(DeserializationContext ctxt,
            String typeId) throws IOException
    {
        JsonDeserializer<Object> deser = _deserializers.get(typeId);
        if (deser == null) {
            /* As per [Databind#305], need to provide contextual info. But for
             * backwards compatibility, let's start by only supporting this
             * for base class, not via interface. Later on we can add this
             * to the interface, assuming deprecation at base class helps.
             */
            JavaType type = _idResolver.typeFromId(ctxt, typeId);
            if (type == null) {
                // As per [JACKSON-614], use the default impl if no type id available:
                deser = _findDefaultImplDeserializer(ctxt);
                if (deser == null) {
                    deser = _handleUnknownTypeId(ctxt, typeId, _idResolver, _baseType);
                }
            } else {
                /* 16-Dec-2010, tatu: Since nominal type we get here has no (generic) type parameters,
                 *   we actually now need to explicitly narrow from base type (which may have parameterization)
                 *   using raw type.
                 *
                 *   One complication, though; can not change 'type class' (simple type to container); otherwise
                 *   we may try to narrow a SimpleType (Object.class) into MapType (Map.class), losing actual
                 *   type in process (getting SimpleType of Map.class which will not work as expected)
                 */
                if ((_baseType != null)
                        && _baseType.getClass() == type.getClass()) {
                    /* 09-Aug-2015, tatu: Not sure if the second part of the check makes sense;
                     *   but it appears to check that JavaType impl class is the same which is
                     *   important for some reason?
                     *   Disabling the check will break 2 Enum-related tests.
                     */
                    type = ctxt.getTypeFactory().constructSpecializedType(_baseType, type.getRawClass());
                }
                deser = ctxt.findContextualValueDeserializer(type, _property);
            }
            _deserializers.put(typeId, deser);
        }
        return deser;
    }

But I don't understand Jackson's architecture to really say for sure.

Is this intended behavior or a bug?

@cowtowncoder
Copy link
Member

I can't say for sure without spending time in digging through this, but it is safe to say that combination of generics with polymorphic types may well lead to problems, and that I would recommend caution in use.

Other than that what I'd need is a full reproduction, since how deserialization is used matters a lot: for example, root value handling with generics is often more problematic than that of types where root value is not parameterized, but some of properties are generic.

@clin88
Copy link
Author

clin88 commented Apr 11, 2016

Sure. I'll try to carve out time for to give you a good repro. I need some time to narrow down a minimal case.

@cowtowncoder
Copy link
Member

@clin88 much appreciated if you are able to do that.

@cowtowncoder
Copy link
Member

One thing on example shown: use of different property at different levels of class hierarchy (that is, different for base and subtypes) is not supported: they have to be same. Typically subtypes do not override @JsonTypeInfo, as values therein should not change. In this case Rule indicates property type whereas ConstraintRule uses "operator".
Whether this causes the problem I don't know, although changing resolution type will also not work well.

I guess another way to put this is that no chaining is used with polymorphic type handing, so it is not possible to have one phase to resolve into intermediate type, another into actual type (or another intermediate type). Custom handler would need to be indicated at base type that is used as deserialization base (nominal type).

@cowtowncoder
Copy link
Member

Multiple levels not supported, no plans to support out of the box, even with Jackson 3.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants