The recently added discriminator and discriminatorValue properties makes it possible to create something that's sort of like a discriminated union style of subtyping, but it's not as tight as it ought to be.
The goal is to create a form of supertype that meets the need for multiple types that are not in an extensionOf relationship to nevertheless satisfy some other common type. This is similar to the use of "marker" interfaces in Java that do not contribute any required members. Any other type can be made to satisfy (in the instanceof meaning) such an interface simply be declaring that the type implements the marker interface.
In our case, we need more than just marker interfaces, because of the need for a delegating factory in the supertype. I.e. the supertype must give rise to a generated class, not just a generated interface, of which the factory becomes a generated static member. This factory is responsible for constructing an instance of the correct subtype when encountering a JSON object in a context requiring the supertype, during overlay construction.
This is where discriminators come, in and why this ends up looking quite a bit like a discriminated union. The supertype factory must delegate to a subtype factory, but to do that it must be able to select the correct subtype. The discriminator property in the supertype specification defines a path, relative to the JSON node to which the factory is applied, and from which a string value is obtained. That string value is compared with the declared discriminatorValues of the subtypes (defaulting to the simple name of the generated subtype interface) to decide which subtype factory to use.
In order to support this more fully, we need something beyond extensionOf to establish this other form of subtyping relationship. At present, extensionOf can be used, but it forces the subtypes to expend their sole Java extends opportunity for this use. We'll now support the following new type properties:
union: boolean, false by default. true means that this type is intended to be a union type. It is an error for such a type to declare any fields, and its implementing class will be made uninstantiable.
unionMember, null by default. If this is provided, it must name a union type. This type's factory will be one of the delegatees available to the union factory.
Additional rules:
A type may be a union member of multiple union types
A type may not be an extensionOf a union type
A type may be an extensionOf a union member type, and in that case, the subtype's factory also becomes one of the available delegatees of any union type of which the supertype is a union member.
Supertypes - whether union types or not - will continue to be equipped with factories that can delegate to their subtype (and sub-subtype and so-on) factories.
When a JSON structure is being parsed, if a union type is required but a discriminator value is not present or not equal to the desclared value for one of the member types or any of its subtypes, the overlay value will be of the union type and will be treated as not-present. This should be considered an erroneous JSON structure, and it should be reported in validation, but goal of a lenient "best effort" parser requires this not to cause an outright failure to parse. This is the only situation in which a union type (or a non-union type marked as abstract) will be produced.
A couple of other problems that have emerged with the existing support for discriminators will be addressed in the work on this issue:
Delegating constructors will be generated so that the cases in their switch statements occur in a deterministic order, so regeneration doesn't randomly create apparent source changes.
Discriminator values will be used only by factories working on JSON nodes, not factories that work on existing overlay values. The latter will always use the provided overlay's simple type. This problem didn't show up until non-default discriminator values were employed in a type specification.
The recently added
discriminator
anddiscriminatorValue
properties makes it possible to create something that's sort of like a discriminated union style of subtyping, but it's not as tight as it ought to be.The goal is to create a form of supertype that meets the need for multiple types that are not in an
extensionOf
relationship to nevertheless satisfy some other common type. This is similar to the use of "marker" interfaces in Java that do not contribute any required members. Any other type can be made to satisfy (in theinstanceof
meaning) such an interface simply be declaring that the typeimplements
the marker interface.In our case, we need more than just marker interfaces, because of the need for a delegating factory in the supertype. I.e. the supertype must give rise to a generated
class
, not just a generatedinterface
, of which the factory becomes a generated static member. This factory is responsible for constructing an instance of the correct subtype when encountering a JSON object in a context requiring the supertype, during overlay construction.This is where discriminators come, in and why this ends up looking quite a bit like a discriminated union. The supertype factory must delegate to a subtype factory, but to do that it must be able to select the correct subtype. The
discriminator
property in the supertype specification defines a path, relative to the JSON node to which the factory is applied, and from which a string value is obtained. That string value is compared with the declareddiscriminatorValue
s of the subtypes (defaulting to the simple name of the generated subtype interface) to decide which subtype factory to use.In order to support this more fully, we need something beyond
extensionOf
to establish this other form of subtyping relationship. At present,extensionOf
can be used, but it forces the subtypes to expend their sole Javaextends
opportunity for this use. We'll now support the following new type properties:union
: boolean,false
by default.true
means that this type is intended to be a union type. It is an error for such a type to declare any fields, and its implementing class will be made uninstantiable.unionMember
,null
by default. If this is provided, it must name a union type. This type's factory will be one of the delegatees available to the union factory.Additional rules:
extensionOf
a union typeextensionOf
a union member type, and in that case, the subtype's factory also becomes one of the available delegatees of any union type of which the supertype is a union member.abstract
) will be produced.A couple of other problems that have emerged with the existing support for discriminators will be addressed in the work on this issue:
switch
statements occur in a deterministic order, so regeneration doesn't randomly create apparent source changes.