Skip to content

dpetrovych/jackson-databind-implicit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jackson Implicit Polymorphic Type Handling (PTH)

Build Status

Usage

@JsonImplicitTypes
@JsonSubTypes({
    @JsonSubTypes.Type(value = FixedReward.class),
    @JsonSubTypes.Type(value = VariableReward.class)})
interface Reward { }

class FixedReward implements Reward {
    public int value;
}

class VariableReward implements Reward {
    public int min, max;
}

void example() {
    ObjectMapper mapper = new ObjectMapper().registerModule(new ImplicitPolymorphismTypeHandleModule());
    
    ArrayList<Reward> rewards = mapper.readValue(
        "[{\"value\":40},{\"min\":35,\"max\":45}]", 
        new TypeReference<ArrayList<Reward>>() {});

    System.out.println(rewards.toString()); // [FixedReward@1, VariableReward@2]
}

Motivation

JSON polymorphic type handling in strongly-typed runtime environments (such as JVM) is usually done by adding a type name as a meta field (also known as discriminator).

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="@type")
interface Reward {}

class FixedReward implements Reward {
    public int value;
}
// Serialized as: {"@type": "FixedReward", "value": 40}

class VariableReward implements Reward {
    public int min, max;
}
// Serialized as: {"@type": "VariableReward", "min": 10, "max": 70}

Meanwhile in dynamic-typed runtime environments a concrete type usually inferred by existence of the properties. Types above can be modeled in TypeScript as a union type:

type FixedReward = {value: Number}
type VariableReward = {min: Number, max: Number}
type Reward = FixedReward | VariableReward

const r1: Reward = {min: 0, max: 1};
const r2: Reward = {value: 1};

The aim of the package is to allow interoperability in heterogeneous systems for discriminated union types without explicit discriminator.

Favor explicit type handling

Resort to implicit type handling only if for whatever reason you can't change the JSON contract.

Having an explicit discriminator is considered a better practice, as it allows to validate and infer type much simpler across all type of runtime environments.

In OpenAPI schema definition for it's possible to define discriminator property to help clients infer an object type (see §Discriminator in Inheritance and Polymorphysm).

It allows a code generation tools (such as openapi-generator) to better separate types. There are messages already claiming support of inheritance without discriminator.propertyName won't be supported for Java (issue).

It's considered good practice in TypeScript to use tagged (or discriminated) unions for custom union types. It makes type guards a lot simpler. See more in TypeScript Deep Dive.

So example above is better to be modeled as:

Java
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = FixedReward.class, name = "fixed"),
    @JsonSubTypes.Type(value = VariableReward.class, name = "variable")
})
interface Reward {}

class FixedReward implements Reward {
    public int value;
}
// Serialized as: {"type": "fixed", "value": 40}

class VariableReward implements Reward {
    public int min, max;
}
// Serialized as: {"type": "variable", "min": 10, "max": 70}
TypeScript
type FixedReward = {type: "fixed", value: Number}
type VariableReward = {type: "variable", min: Number, max: Number}
type Reward = FixedReward | VariableReward

About

Jackson Implicit Polymorphic Type Handling

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages