question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Deserialization of `@JsonTypeInfo` annotated type fails with missing type id even for explicit concrete subtypes

See original GitHub issue

When attempting to deserialize to a concrete class that is part of a polymorphic type hierarchy, an InvalidTypeIdException is thrown if the JSON does not contain the type id field. Example:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Foo.class, name = "foo"),
    @JsonSubTypes.Type(value = Bar.class, name = "bar")})
public interface Base {}

public class Foo implements Base {}

public class Bar implements Base {}

ObjectMapper mapper = new ObjectMapper();

mapper.readerFor(Foo.class).readValue("{}"); // throws InvalidTypeIdException
mapper.readValue("{}", Foo.class); // throws InvalidTypeIdException

While I understand why this happens, as Jackson is finding the JsonTypeInfo / JsonSubTypes annotations on the interface, it is counterintuitive to me. In this instance, I am instructing the mapper as to the specific concrete class to deserialize to, so consulting those annotations seems unnecessary. Perhaps checking if the class / type supplied to readerFor / readValue matches exactly one of the classes listed in JsonSubType could be a fallback if the type id property is not found?

So far, the only workaround I’ve found is to do something like this:

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public class Foo implements Base {}

but then that means serializing a Foo instance would not get the type id property. Perhaps a custom TypeIdResolver or SubTypeResolver could also be used, but having the described behavior baked in seems like a sensible default to me. Thoughts?

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:11
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

6reactions
kilinkcommented, Dec 4, 2020

Here’s an example. We have a tool that generates Java / Kotlin classes from GraphQL schemas, so given an example trivial schema:

interface Animal {
  name: String
}

type Dog implements Animal {
  name: String
  barkVolume: Int
}
        
type Cat implements Animal {
  name: String
  lives: Int
}

type Query {
  animals: [Animal]
  dogs: [Dog]
  cats: [Cat]
}
       

We might generate the following for the Animal interface so that query for animals works correctly:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "__typename"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "Cat")
})
public interface Animal {
  String getName();
}

Now when queries are also built using our codegen tool, we can always arrange for the __typename meta field to be returned, but some users may opt to only use the generated types and build their own queries by hand. To us the generated class even when querying for a specific type like Dog, they need to remember to also always ask for __typename, which seems a little strange to have to do if they are indicating at time of deserialization they know the concrete type based on the query they’re performing.

I can also think of non-GraphQL scenarios, where the client is using generated code and doing similar queries for REST endpoints like /animals, /dogs, and the server component is perhaps not using the same classes at all or is written in a different language. Yes, the /dogs endpoint could return JSON with type ids, but it seems redundant to have to do that when the client is specifically asking for a “concrete type” in this case.

2reactions
wetnebcommented, Mar 30, 2022

I would also find this feature very useful.

I want to share a workaround which seems to work fine in my case: to annotate the subclasses with the same @JsonTypeInfo, but including a defaultImpl. For instance:

// Top-level interface

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "__typename"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "Cat")
})
public interface Animal {
  String getName();
}

// Subclasses

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "__typename",
    defaultImpl = Dog.class
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "Dog")
})
public class Dog implements Animal {
    // ...
}

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "__typename",
    defaultImpl = Cat.class
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Cat.class, name = "Cat")
})
public class Cat implements Animal {
   // ...
}

It is not exactly pretty, but it seems to be working as expected.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to identify the missing type id in Jackson error?
UPDATE: BusinessContact class should be annotated with @JsonTypeName("business") and PersonContact class with @JsonTypeName("personal") ...
Read more >
JsonTypeInfo (Jackson-annotations 2.13.0 API) - FasterXML
Annotation used for configuring details of if and how type information is used with JSON serialization and deserialization, to preserve information about ...
Read more >
JsonTypeInfo.java - Android Code Search
Means that logical type name is used as type information; name will then need. * to be separately resolved to actual concrete type...
Read more >
On Jackson CVEs: Don't Panic — Here is what you need to ...
Easiest way is to use annotation @JsonTypeInfo , using one of two ... When doing this, serializer will add a type identifier in...
Read more >
Annotation Type JsonTypeInfo - Red Hat Customer Portal
Annotation used for configuring details of if and how type information is used with JSON serialization and deserialization, to preserve information about ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found