Jackson-databind: Add `MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL` to use declared base type as `defaultImpl` for polymorphic deserialization

Created on 2 Oct 2015  路  25Comments  路  Source: FasterXML/jackson-databind

I use @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type") for interfaces and abstract classes and it works as expected.

Now I have a case where the JSON string does not contain the 'type' property (external interface) but I know the concrete class to which this JSON string should be mapped.

When I now use objectMapper.readValue(jsonString, ConcreteClass.class) then I get an exception that the 'type' property is missing. That's bad because I tell Jackson that the 'type' is 'ConcreteClass.class' so I want that Jackson tolerates the missing 'type' property. In other words: Please use the given class as 'defaultImpl' (see JsonTypeInfo attribute defaultImpl) if no JsonTypeInfo defaultImpl attribute was set but a concrete class was given.

Or is there another way to define a 'defaultImpl' when using readValue()?

Thank you!

Example:

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
public interface MyInterface {
  String getName();
  void setName(String name);
}

public class MyClass implements MyInterface {
  private String name;
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

This works:

{
  "name": "name",
  "type": ".MyClass"
}
objectMapper.readValue(jsonString, MyInterface.class);

This not (but it would be very nice if you can make it work):

{
  "name": "name"
}
objectMapper.readValue(jsonString, MyClass.class);

Most helpful comment

You can specify "default type" with @JsonTypeInfo(defaultImpl=DefaultImplementClass.class)

As to whether it'd be possible to force use of actual sub-class... I don't know off-hand. It is an interesting idea, and if that is possible to implement could make sense. But I'd have to see how code path works for this particular case; polymorphic type handling is quite specialized system.

All 25 comments

You can specify "default type" with @JsonTypeInfo(defaultImpl=DefaultImplementClass.class)

As to whether it'd be possible to force use of actual sub-class... I don't know off-hand. It is an interesting idea, and if that is possible to implement could make sense. But I'd have to see how code path works for this particular case; polymorphic type handling is quite specialized system.

Using @JsonTypeInfo(defaultImpl=DefaultImplementClass.class) is not enough for the provided example. The parent has the @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type") annotation therefore the child needs the @JsonTypeInfo(defaultImpl=DefaultImplementClass.class use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type") annotation. This is bad because the child needs the knowledge about the parent. If the parent changes his @JsonTypeInfo annotation all children must also be changed if they declare a 'defaultImpl'.

Property defaultImpl is typically defined in parent class, not child; in fact, using it in child (sub-class, that is) is not likely to work well. This because nominal type declarations usually use the base class.

Regardless, I think the idea of having a way to "override" use of whatever default exists (or does not exist) is a good one. It's just the question of how this could be implemented through the code flow.

Property defaultImpl is typically defined in parent class, not child is not working in 2 cases (real world examples):

1.) You have multiple children and 2 of them are for 2 external interfaces where the 'type' property is missing. Your 'defaultImpl' in the parent workaround does not work here.
2.) The parent does not see the children therefore it can't define the 'defaultImpl'.

What I am saying is that the usage of specifying different defaultImpl may not actually physically work from Jackson perspective. I did not mean that it would not necessarily be useful feature.

Specifically the only thing Jackson sees on deserialization is whatever nominal type is. Whatever annotation on that type (or on its supertype) defines is what gets used; information from subtypes will not be used.

I think we are drifting off the subject. This issue complains about that Jackson does not use the given information (the concrete class) when I call objectMapper.readValue(jsonString, ConcreteClass.class) :)

Correct.

Not sure if this is possible to implement; the challenge being that root type being passed to ObjectReader / ObjectMapper is really only used for finding JsonDeserializer to use and not reliably available for further processing (although it might be available via currentValue). But perhaps combination of considering:

  1. Non-existing of defaultImpl (I think?)
  2. Parsing being at root level (or close enough? With "As-property", should be root JSON Object)
  3. Current value being of proper, concrete type

could allow resolving intended type.

Can the objectMapper.readValue() method check if defaultImpl is not set and in this case set defaultImpl with the provided class? Or a new method?

@Martin-Wegner I think that ideally it should work with existing methods. Value of defaultImpl can not be easily changed, as a mechanism, because definitions are shared. If it was possible that would be a reasonable way to go. But since only root value handling is affected it may be possible to avoid having to pass this information very far; root-handling accesses JsonSerializer (and/or TypeSerializer) and might be able to directly introspect situation.

Couldn't this be solved by allowing the type property to be missing and passing the requested/field type in the DatabindContext in the same manner as the view currenty is?

It would then be up to the TypeIdResolver to make use of this as the default value.

I guess a new (sub) context would have to be created for every field - or the type could be a stack which would allow for more advanced custom resolving (e.g. C contained in A defaults to X but C contained in B defaults to Y).

@Raniz85 I suspect it is only necessary to handle the direct root value type, and not propagate it along, so it could be even simpler possibly.

That will only solve objectMapper.readValue(json, ConcreteClass.class) but not

class B {
    private ConcreteClass member;
}

or am I mistaken?

@Raniz85 correct.

What the status? I also got this problem.
Microsoft Graph API have this call: https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/group_list
It returns array of Group objects:

{
  "value": [
    {
      "id": "id-value",
      "description": "description-value",
      "displayName": "displayName-value",
      "groupTypes": [
        "groupTypes-value"
      ],
      "mail": "mail-value",
      "mailEnabled": true,
      "mailNickname": "mailNickname-value",
      "onPremisesLastSyncDateTime": "onPremisesLastSyncDateTime-value",
      "onPremisesSecurityIdentifier": "onPremisesSecurityIdentifier-value",
      "onPremisesSyncEnabled": true,
      "proxyAddresses": [
        "proxyAddresses-value"
      ],
      "securityEnabled": true,
      "visibility": "visibility-value"
    }
  ]
}

The Group object inherits from DirectoryObject but in this request, it does not contain type @odata.type:#microsoft.graph.group

@Hronom this may be surprising turn of events but the simple heuristics to use is this: if there are no updates here, there is generally nothing going on. And this is indeed exactly what is going on here: nothing. It is also unlikely much will be happening any time soon, without contributions.

Thank you for the hint regarding @JsonTypeInfo(defaultImpl=...). Here, it works perfectly when being used in subclasses and also solves my stackoverflow question: How to deserialize JSON having refrences to abstract types in Jackson.

Sorry, I'm bit confused. I have the issue, and I only care about the root type. Some comments in this thread seem to imply there is a way using the API to indicate that if the top level object has no type info, we could use the class we pass to ObjectMapper. Where can I find information about this technique? Thanks!

No, type information is expected at all levels if it is expected by nominal type.
So although there is a way to specify expected type, this would only be used as the "base type" from which actual polymorphic type is derived.

Hi all!

I have a problem similar to this, buy in my case I'm using a CUSTOM type and a TypeIdResolver (@JsonTypeIdResolver). Could I access the suggested concrete class within my TypeIdResolver? (maybe as a ContextAttribute ?)

You can use
objectMapper.enable(DeserializationFeature.USE_BASE_TYPE_AS_DEFAULT);
to fix this issue.

Quick note: changed to MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL since this can not be changed on per-call basis (DeserializationFeatures may be changed, but it would not take effect due to caching, and this could be confusing).

Fix will be in 2.9.6 although I am not bit unsure whether this will fully work as intended.
May make it 2.x only and revisit for 3.0.

any idea when 2.9.6 is expected to release?

Plan was "by end of May", although it may slip slightly into June now. But Very Soon Now, still, just one critical thing I will have to resolve.

@cowtowncoder good work, now it works for me as expected :)

Was this page helpful?
0 / 5 - 0 ratings