-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Proposal for allOf, anyOf and oneOf
This is a proposal for the implementation of oneOf
, anyOf
and allOf
. It is broken into a set of rules for rewriting schemas containing allOf
and anyOf
types into simpler schemas and a method for deserializing oneOf
rules into the correct type. These rules are specified in a high level way and a set of problematic schemas are included to provide details.
NOTE: not
is not considered in this proposal. Suggestions on how to handle that keyword are welcome.
Since allOf
and anyOf
deal in the composition of types, they could be handled by rewriting the types containing them, so that they no longer appear in the schema. Once this is done, the existing rules for type generation could be employed. The rules here are specified loosely, since some schema combinations could lead to problems that are combinatoric in nature. For instance, an allOf
containing several oneOf
s could result in a cross product operation happening. See the Problematic Schemas section for examples.
-
description
: the union of thedescription
properties, joined withand
. -
javaInterfaces
: the intersection of the inputjavaInterface
array. -
extends
: the intersection of the inputextends
properties. This should consider the most specific common superclasses. -
properties
: the union of the properties. -
required
:- for booleans, the logical or of the 'required' properties.
- for arrays, the union of the 'required' arrays.
-
type
: the common type ornull
if the types differ.
-
description
: the union of thedescription
properties, joined withor
-
javaInterfaces
: the union of the inputjavaInterface
arrays. -
extends
: the intersection of the inputextends
properties. This should consider superclasses. -
properties
: the union of the properties. -
required
:- for booleans, the logical and of the 'required' properties.
- for arrays, the intersection of the 'required' arrays.
-
type
: the common type or the schema becomes aoneOf
with values for each type.
Schemas with oneOf
nested in allOf
could produce a large number of types, if the implementation attempts to produce a type for every possible valid combination.
{
"allOf": [
{
"oneOf": [
{ "ref": "a.json" },
{ "ref": "b.json" }
],
"oneOf": [
{ "ref": "c.json" },
{ "ref": "d.json" }
]
}
]
}
To avoid this, oneOf
s should be treated with these rules when found in allOf
:
- the result of operations on
oneOf
nested in anallOf
will result in oneoneOf
being created, with one nested schema for eachtype
found in all the sub schemas. - common 'type' schemas are merged using the rules of
anyOf
- if the oneOf contains only one schema, it is collapsed
These rules result in at most one type being created (for { "type": "object" }
). All other types involved will come from the existing set of types (Integer, String, etc.)
Schemas where anyOf
has nested schemas with co-prime cycle lengths could produce a large number of types, if the implementation attempts to produce every unique hierarchy level.
{
"type": "object",
"properties": {
"child": {
"anyOf": [
{ "ref": "#/definitions/a" },
{ "ref": "#/definitions/b" }
]
}
},
"definitions": {
"a": {
"type": "object",
"properties": {
"child": { "ref": "#/definitions/c" },
"a": { "type": "string" }
}
},
"b": {
"type": "object",
"properties": {
"child": { "ref": "#/definitions/d" },
"b": { "type": "string" }
}
},
"c": {
"type": "object",
"properties": {
"child": { "ref": "#/definitions/a" },
"c": { "type": "string" }
}
},
"d": {
"type": "object",
"properties": {
"child": { "ref": "#/definitions/e" },
"d": { "type": "string" }
}
},
"e": {
"type": "object",
"properties": {
"child": { "ref": "#/definitions/b" },
"e": { "type": "string" }
}
}
}
}
Currently, I do not know the best way to resolve this elegantly. Fully collapsing types involved in cycles may be the best option. That would result in this being the rewritten form of this schema.
{
"type": "object",
"properties": {
"child": {
"type": "object",
"properties": {
"child": { "ref": "#/properties/child" },
"a": { "type": "string" },
"b": { "type": "string" },
"c": { "type": "string" },
"d": { "type": "string" },
"e": { "type": "string" }
}
}
}
}
Custom deserialization will be required to handle oneOf
, since properties can only specify one type. To accomplish this, the needed deserialization code will be statically generated in the generated types.
Using a combination of @ JsonProperty
and a nested static JsonDeserializer, oneOf
could easily be implemented for Jackson. The generated code would follow this general pattern.
JCodeModel codeModel = new JCodeModel();
try {
URL source= new URL("file:src/main/resources/schema/DummyApi.json");
GenerationConfig config = new DefaultGenerationConfig() {
@Override
public boolean isGenerateBuilders() {
return true;
}
public SourceType getSourceType(){
return SourceType.JSON;
}
};
SchemaMapper mapper =new SchemaMapper(new RuleFactory(config, new GsonAnnotator(config), new SchemaStore()), new SchemaGenerator());
mapper.generate(codeModel, "Accession", "com.pojogenerated", source);
File dir = new File("src/main/java");
if(dir.exists()){
System.out.println("dir available");
codeModel.build(dir);
}else{
System.out.println("dir not available");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
GSON 2.3 introduced a @ JsonAdapter
annotation. This could be used to bind a property to a nested static TypeAdapter
specifically for that type, in that context. There is currently not an example for this.