Open CasperDB opened 9 years ago
No, I'm afraid these are not supported.
It would be useful if you could explain how you would expect this to work in this case? What Java would you like to see produced?
(see also #91)
I'm not sure what I want it to look like yet... My intention is to replace a jaxb object with a jackson object. Something similar to the xsd choice @XmlElement:
<xsd:element name="name" type="xsd:string" minOccurs="0"/>
<xsd:choice>
<xsd:element name="address" type="address"/>
<xsd:element name="phone-number" type="phoneNumber"/>
<xsd:element name="note" type="xsd:string"/>
</xsd:choice>
private String name;
@XmlElements(value = {
@XmlElement(name="address",
type=Address.class),
@XmlElement(name="phone-number",
type=PhoneNumber.class),
@XmlElement(name="note",
type=String.class)
})
I'm currently looking at Jackson annotations that might be useful.
Maybe you could blow oneOf out into several properties.
@JsonProperty("idString")
@Nullable
private String idString;
@JsonProperty("idNumber")
@Nullable
private String idNumber;
public void setIdString(String newIdString)
{
idNumber = null;
idString = newIdString
}
...
It would require a custom gson/jackson serialization/deserialization step for that part though. It could be pretty generic and we could write it once into the jsonschema2pojo library.
Maybe oneOf rule can be implemented as class inheritance. In the example above, it could look like this:
public abstract class id {
}
public class child1 extends id {
public String type;
}
public class child2 extends id {
public int type;
}
I think allOf and anyOf do not make sense in java.
True, but we have to work with gson/Jackson right? How will they know how to deserialise the json and pick the right subclass?
On Mon, 10 Aug 2015 17:47 amilinko notifications@github.com wrote:
Maybe oneOf rule can be implemented as class inheritance. In the example above, it could look like this:
public abstract class id { } public class child1 extends id { public String type; } public class child2 extends id { public int type; }
I think allOf and anyOf do not make sense in java.
— Reply to this email directly or view it on GitHub https://github.com/joelittlejohn/jsonschema2pojo/issues/392#issuecomment-129502127 .
Yes, it would require Jackson (not sure about gson). There is an example of serialization/deserialization here. It is described in examples 4, 5 and 6.
Looks like you'd end up doing reflective stuff like:
if (anInstance.isKindOfClass(child1.class) {
...
There's still a big question mark over how you decide which one to decode it as. Do you run up a JSON schema validator and see which one it passes? You probably need some extension to json schema to mark which one it is as some kind of enum.
oneOf is really nice from a validation side but really awkward from a model side. If you look at swagger, they've ditched oneOf support because of this. here's an alternate schema definition that achieves the same thing I think:
{
"type": "object",
"properties": {
"name": {
"description": "The advertised product",
"type": "string"
},
"id" : {
"properties": {
"stringId" : { "type": "string", "maxLength": 5 },
"numberId" : { "type": "number", "minimum": 0 }
},
"maxProperties" : 1,
"minProperties" : 1
}
}
}
I was considering submitting a PR for oneOf, anyOf, and allOf that just generates the fields required to properly bind the data, so that at least those constructs are usable. Solid support for these semantics will probably require providing a runtime dependency with (de)serializers for Jackson and objects to model these types.
If a runtime dependency is something the project could support, then code that works like this (for the id example) could be generated:
OneOf id = pojo.getId();
// test the type
pojo.getId().is(Double.class)
// cast
Double number = pojo.getId().as(Double.class)
// with java 8 and functional interfaces
pojo.getId().isType(Double.class).then(id->...)
oneOf does make sense in Java if you think about polymorphic types. For example:
"dataSourceBindings": { "type": "array", "items": { "oneOf": [ { "genericDataSourceBinding": { "type": "object", "properties": { "dataSourceName": { "type": "string" } }, "required": [ "dataSourceName" ] } }, { "multiDataSourceBinding": { "type": "object", "properties": { "dataSourceName": { "type": "string" }, "algorithmType": { "type": "string", "enum": [ "Failover", "Load-Balancing"] } }, "required": [ "dataSourceName" ] } } ] } }
What I would have hoped to see generated was a DataSourceBinding class, and two subclasses of it: GenericDataSourceBinding and MultiDataSourceBinding.
@rpatrick00 That is an interesting idea. With $ref
in the mix, the types can form a cyclic graph and not just tree structures, so linking oneOf
to single inheritance will be problematic.
Maybe so but the current behavior is a problematic too.
The object containing the dataSourceBinding property has this:
List
and nothing else inside this is generated...not DataSourceBinding, GenericDataSourceBinding, or MultiDataSourceBinding. I can understand the fact that you don;t want to deal with polymorphism but it should, at minimum, generate the classes for types defined inside the oneOf...
I have created some basic test cases and an empty rule in my fork feature_unions. Is there any interest in working on this as a group?
I would be happy to contribute to oneOf, provided I understand the various use cases that you want to support. For me, the most important use case is for polymorphic object/array types.
@rpatrick00 is the example you have provided consistent with draft 4 of the specification? It seems like you have added an extra level under oneOf
.
It seems like it will be hard to alter the types being composed in a oneOf
, anyOf
, or allOf
. Perhaps we could go the other direction and use Java Intersection Types when defining the method signature. For example:
public interface Named {
public String getName();
}
public interface Described {
public String getDescription();
}
a.json
{
"type": "object",
"javaInterfaces": ["Named", "Described"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"a": { "type": "string" }
}
}
b.json
{
"type": "object",
"javaInterfaces": ["Named", "Described"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"b": { "type": "string" }
}
}
c.json
{
"type" "object",
"properties" : {
"children": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "a.json" },
{ "$ref": "b.json" }
]
}
}
}
}
C.java
public class C {
public <T implements Named & implements Described> List<T> getChildren() { ... }
public <T implements Named & implements Described> void setChildren( List<T> children ) { ... }
}
This would probably require providing some special Jackson (de)serializers.
I am not sure Java Intersection Types accomplish the same thing as Polymorphism. Your c.json shows what I need but in my case, the only "shared interface" is the superclass and the subclasses share no other common "interface" or properties (if they did, they would be in the super type, wouldn't they).
Other parts of my code are accomplishing this starting with Java and JAXB/Jackson bindings. In this module, I need to start with JSON and was hoping to generate the Java code. I was able to make something close to what I wanted work (the generated code uses List<Object> instead of List<DataSourceBinding> but it works for me).
We could also constrain on the common superclass, so that wouldn't be a problem. I do think that the common superclass would need to come from the way the children are specified and not from their participation in a oneOf
. Otherwise, we would end up in a situation where types participating in multiple oneOf
statements would need multiple superclasses.
I have started a test project locally, to see what can be done for Jackson support. It seems like we would need some annotations like the following to properly support oneOf
:
@OneOf(
@TypeOption(type=A.class),
@TypeOption(type=B.class)
)
public <T> T value;
I will push that up to github when I have some minimal examples working.
NOTE: Due to Java Annotations limitations on cycles, etc., custom annotation bindings is probably not the way to go. Instead, purpose built deserializers is probably the best way to make Jackson handle these Json Schema constructs.
It looks like static inner JsonDeserializers may be the best way to implement these features. I have created a gist showing an example of what we could create for oneOf. I didn't polish this, but it works.
The basic pattern would be:
public class GENERATED_TYPE {
public static FIELD$JsonDeserializer extends JsonDeserializer<FIELD_TYPE> {
@Override public FIELD_TYPE deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec codec = jp.getCodec();
TreeNode tree = jp.readValueAsTree();
// pick type here, continue parse with codec.
}
@JsonDeserialize(using=FIELD$JsonDeserializer.class)
private FIELD_TYPE FIELD;
...
}
@joelittlejohn really interested in what you think of this.
I'm not sure that this works as a general approach, how do you pick the type in the general case? Were you suggesting these deserializers are generated by the tool or are written manually to choose the type based on whatever manual checks are required?
I see this as two problems, computing the correct merged schema and getting Jackson to deserialize what was computed. For the deserialization part, I am proposing generating the deserializers in the model. Determining a reasonable and correct way to represent the types seems like a challenge and shouldn't be attempted unless we can work with the types generated.
As far as computing the types, a wiki page should probably be started. High level, the needed rules seem something like:
For allOf
:
allOf
is always generated.$ref
).For anyOf
:
anyOf
is always generated.oneOf
are applied.For oneOf
:
boolean isThisType(JsonTree tree)
).oneOf
.This strategy would only work for Jackson. I have no clue how GSON deals with types like this. I guess Map
, List
, or the target (de)serializers generic JSON representation could be returned for other tools.
It looks like something similar can be accomplished with GSON 2.3+ using @JsonAdapter
and TypeAdapter
.
I have added a page to the wiki with a proposal for implementing these keywords.
I had folked and created a simple solution for "oneOf", see oneOfTypesProduceObjects Test. No inheritance,just enum and fields mapping. Basic concept is finding a best match based on required and optional fields, because sometimes there may not have any common fields in "oneOf".
It would be nice if oneOf
was supported, e.g. using JsonSubTypes. Even if only supported by Jackson it would be a win.
@ben-manes if the functionality was constrained to a single discriminator field, it probably would not be hard to support both Jackson and Gson. For anything more complex, you could just bail out to a generic type like JsonNode for Jackson or JsonElement for Gson.
After attempting more complete support for oneOf/anyOf/allOf several times, I do not think it is feasible. There is just too much impedance between what Java object models can represent and what JSON Schema can specify. The project might as well settle for very limited support.
Trying to force Java OO onto JsonSchema definitely doesn't work. The codegen works beautifully for RPCs, but for more complex cases I bail out to JsonPath. All I'd hope for is more support for the easy cases rather than trying to bridge paradigms completely.
Hi all Guys, I need this feature and ready to participate. I have some questions
@s13o I put that proposal out there and the consensus seems to be that these keywords are just too problematic to generate good solutions for. If you want basic support, I would create a PR where these keywords generate fields of Object. That could be achieved with minimal work and should result in the mappers injecting their generic node types.
Proposal is detailed enough. It more than 1 year old already. Let's stop talking and start to do something. In a feature-branch.
What about a getter that returns a railroad oriented data type? https://fsharpforfunandprofit.com/rop/
For example,
"oneOf": [
{ "type": "number" },
{ "type": "string" }
]
could generate a getter that returns something like Functional Java's Either data type.
In practice you'd probably end up generating a class for each field that can be multiple types. Something more like:
interface SomethingEither {
default Optional<Number> number() { return Optional.empty() }
default Optional<String> string() { return Optional.empty(); }
}
So you would get code generated with a method something like this:
SomethingEither getSomethingEither() {
if(something instanceOf Number) {
return new SomethingEither() { public Optional<Number> number() { return Optional.of((Number) something); } };
}
if(something instanceOf String) {
return return new SomethingEither() { public Optional<String> string() { return Optional.of((String) something); } };
}
}
@JLLeitschuh I like this. I can imagine SomethingEither having another method marked @JsonCreator
used by Jackson when creating these values from JSON input.
One question remains though - and it's the same challenge with many of the other solutions - how does the @JsonCreator
method make sense of incoming JSON when applied to arbitrarily complex JSON objects?
This schema is easy to accommodate:
"oneOf": [{ "type": "number" },
{ "type": "string" }]
this one not so much:
"oneOf": [{ "type": "object", "properties" : ...},
{ "type": "object", "properties" : ...}]
Lets take a really simple, concrete example:
"oneOf": [{ "type": "object", "properties" : {"foo": {"type: "string"}}},
{ "type": "object", "properties" : {"foo": {"type: "number"}}}]
How do we know which of the Either values should be populated for an incoming piece of JSON? What's the general rule?
You'd have to generate some sort of unnamed type that you'd have to define just like you'd have to do for a field that defines only one object. Jackson would end up serializing into one of these objects and then you'd have to do the same casting thing.
More concretely:
{ "type": "object", "properties" : {"foo": {"type: "string"}}}
would become something like class SomethingAnnonA
and
{ "type": "object", "properties" : {"foo": {"type: "number"}}}
would become something like class SomethingAnnonB
.
The method would be:
SomethingEither getSomethingEither() {
if(something instanceOf SomethingAnnonA) {
return new SomethingEither() { public Optional<SomethingAnnonA> somethingAnnonA() { return Optional.of((SomethingAnnonA) something); } };
}
if(something instanceOf SomethingAnnonB) {
return return new SomethingEither() { public Optional<SomethingAnnonB> somethingAnnonB() { return Optional.of((SomethingAnnonB) something); } };
}
}
I don't really know how jackson works under the hood, I've never worked with jackson's API directly. I've only ever used it through spring where it does most things automagically.
This is the part that's missing:
Jackson would end up serializing into one of these objects
It's not possible for Jackson to choose one of these types. Once you have a value, returning it correctly via your Either type would be possible, but how do you correctly choose which type the value should have in the example I gave?
Try/catch jackson deserializing every possible type that something
could be?
What would the type of something
be by default in the current implementation? A map? A string?
I think you can use @JacksonInject ObjectMapper
into @JsonCreator
methods with correct configuration. Again, not something I've played with.
Hello,
I am jumping in this conversation to see what is the current status. Do you guys think it's actually possible to generate this Java code with feature such as anyOf
?
@JLLeitschuh last proposals seem to do the job to me.
I would like to generate Java classes for this pretty big schema: https://github.com/vega/schema/blob/master/vega-lite/v2.0.0-beta.10.json that heavily use the anyOf
feature.
+1 for allOf, oneOf, anyOf support...
With XSD, the concept of choice exists also, and when building the Java object based on XSD with JAXB, we get an object with getter and setter of all sub objects. In the java application, you can manage this choice. If we execute this plugin with a oneOf, the object inside oneOf are not generated and it is the worst solution.
Any update about this issue? Especially the "allOf" field ?
In my case I'm trying to generate the Java class corresponding to this schema https://raw.githubusercontent.com/oasis-open/cti-stix2-json-schemas/stix2.1/schemas/sdos/attack-pattern.json
however, since all its properties are inside the "allOf" field, nothing is being generated apart from toString
, hashCode
and equals
methods.
Any update for allOf support?
I'm generating a POJO using the jsonschema2pojo-maven-plugin and need to pass constraints in using oneOf, anyOf & allOf.
When I try the following code in a schema validator it works:
However, the POJO after building has no constraints:
I would like to see some restrictions annotated, is this not supported with the plugin yet?