jsonschema2pojo
jsonschema2pojo copied to clipboard
Support draft-zyp-json-schema-04 (draft-fge-json-schema-validation-00)
Links:
- http://tools.ietf.org/html/draft-zyp-json-schema-04
- http://tools.ietf.org/html/draft-fge-json-schema-validation-00
Notes:
- [x] changes to 'required'
- [x] deprecate 'extends'
- [ ] basic/naive support for 'allOf', 'anyOf' (see issue #17)
- [ ] add aliases for some 'format' values
Original issue: http://code.google.com/p/jsonschema2pojo/issues/detail?id=91
Also, please add basic/naive support for "oneOf". A possible way to implement this:
This could probably be done using a common "JsonSchema" interface type that all schema's implement. This interface could have a method called getSchemaType() which returns a SchemaType enumeration. This schema type enumeration would be used to cast the schema to its actual type (rather than using instanceof). The schema type enumeration value could be generated from the "title" property or the parent property followed by a "_" followed by an index number (or something like that). (The schema class name would be named similarly, so one would know what to cast the JsonSchema to.)
+1 on this, mainly to get proper support for "allOf", now that "extends" is gone.
Yes, pls
Great work here Joe! Support for the "oneOf" use case would be amazing!
Support for v4 features ("allOf", "anyOf", "oneOf", "required" as arrays etc) could be implemented as adapter which would read feature list from http://json-schema.org/draft-04/schema. And current implementation could be converted to adapter with list of rules (org.jsonschema2pojo.rules.Rule) from http://json-schema.org/draft-03/schema. Then client will have to specify adapter to use (could default to draft v3).
allOf
could be implemented as "extends" chain where each next schema overrides the previous. For example "allOf": [{ "$ref": "schema1.json"}, {"$ref": "schema2.json"}, {"$ref": "schema3.json"}]
could be mapped to class Schema3 extends Schema2
and class Schema2 extends Schema1
Implementing anyOf
and oneOf
is trickier.
It may be a bit strange for us to create a chain of extending classes. There's no implied relationship between the 'allOf' classes so although 'extends' has the right effect on the final child class, the parents would have a hierarchy that doesn't make much sense.
I'm thinking the best way to support allOf would simply be to merge all the schemas present inside the allOf value and generate the type from the merged 'super schema'.
For anyOf and oneOf I think the only option will be to create an empty/marker interface, generate a new type for every schema present and make them all implement this. We'd have to add JsonTypeInfo to these classes and a type hint in the JSON data would be required.
Ahoy! how is progress on this front?
@samskiter no progress on this front yet:
https://groups.google.com/forum/#!topic/jsonschema2pojo-users/f5ijBuG04LM
First off - thank you for sharing your generator with the community. Its a very well done implementation that I look forward to carving out some time to contribute to.
What about only supporting a single schema reference when implementing allOf? Basically just support the extends functionality that exists in draft 3. If there is more than one throw an exception.
I don't believe that it is appropriate to try hack support for multiple inheritance into Java. If you have to support that type of schema, I think it's appropriate to create a shim schema for pojo creation that will generate code that will output valid JSON. For those of us playing in the single inheritance world that shouldn't be hard.
For any other feature that isn't supported yet just throw an exception or skip the generation (a command line option of course). This way we can all play with the new draft and move forward.
I understand the mentality of 100% support for a standard but in this case the type system has evolved beyond what java can support. What about adopting a midset of supporting the features that have parity and waiting for the draft to evolve before creating (elegant) hacks?
Again, thanks for your contribution!
:+1: to what @3bird said :wink:
I agree, support of allOf with one schema reference kind of important
I would concentrate on allOf support, there are 2 ways:
- As said above, treat it as extends and allow only one reference, limited.
- Combine all properties together, basically it will not extend anything, but will aggregate all fragments together. Proper inheritance chain can be done via interface(s) “javaInterfaces” as you would normally do in java in such a case.
I can help with this, if needed.
P.S. anyOf or oneOf could piggyback on allOf treatment, there are some questions ( required, etc ) but this is small price to pay vs not to support that at all.
I like (2) - the idea of using interfaces of getters to support the multiple inheritance that jsonschema allows
First of all thanks for this great tool. OneOf addition might be a great support.
@maksimlikharev - something like the following: ?
Very keen to see how I can help in implementing the "allOf"
Given:
{
"id": "http://xxx.xxx.com/schemas/person",
"title": "Person",
"type": "object",
"properties": {
"name": {
"description": "person name",
"type": "string"
}
}
}
{
"id": "http://xxx.xxx.com/schemas/Employee",
"title": "Employee",
"type": "object",
"allOf": [
{ "@ref": "http://xxx.xxx.com/schemas/person" }
],
"properties": {
"employeeID": {
"description": "employee id associated with this person",
"type": "string"
}
}
}
For each JSON object we generate a concrete class... ( perhaps with an option to suppress ?)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"name"
})
public class Person implements IPerson {
...
@JsonProperty("name")
public String getName() {
...
}
}
with the interface class:
public interface IPerson {
String getName();
}
As Employee is composed of "allOf" the interface class
public interface IEmployee extends IPerson {
public String getEmployeeID();
}
such that the Employee class now needs to have a concrete implementation for both Person and Employee
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"name",
"employeeID"
})
public class Employee implements IEmployee{
@JsonProperty("name")
public String getName() {
...
}
@JsonProperty("employeeID")
public String getEmployeeID () {
...
}
}
Have I missed something obvious?
@neilredway How would you GSon/Jackson-annotate the Employee
class so it serializes properly to JSON?
I updated the previous post. I didn't think it would have needed anything extra would it?
I am sure I have missed something....
My bad :bow: I thought Employee
was a composition of embedded objects, each representing a member of the allOf
, but no, it's all top level simple-typed fields :blush:
@neilredway @ddossot We have a bit of a problem with the interface option because things like getters and setters are optional. At the class level (ignoring the interfaces for now) you'd be merging the schemas to create a class containing the superset of fields, right? I think this is a valid approach.
I understand you're trying to introduce some useful polymorphism into your model, but this complicates things a lot. I think you may also be making an OO-related assumption about the schema author's intention when using allOf that may not be true in all cases.
The interface option introduces another case in which Person is generated differently, depending on the contents of other schemas (Should it implement IPerson or not? If it sits alone, it doesn't, if it has others that use it, it does). Whenever we have this situation arise (schemas producing different code depending on the context in which they're used) it creates a lot of confusion.
I think the merging approach will definitely get us useful allOf support.
{
"id": "http://xxx.xxx.com/schemas/person",
"title": "Person",
"type": "object",
"properties": {
"name": {
"description": "person name",
"type": "string"
}
}
}
{
"id": "http://xxx.xxx.com/schemas/employee",
"title": "Employee",
"type": "object",
"allOf": [
{ "@ref": "http://xxx.xxx.com/schemas/person" }
],
"properties": {
"employeeID": {
"description": "employee id associated with this person",
"type": "string"
}
}
}
{
"id": "http://xxx.xxx.com/schemas/candidate",
"title": "Candidate",
"type": "object",
"allOf": [
{ "@ref": "http://xxx.xxx.com/schemas/person" }
],
"properties": {
"candidateID": {
"description": "candidate id associated with this person",
"type": "string"
}
}
}
The generation would look like this:
Person implements IPerson
IEmployee extends IPerson
Employee implements IEmployee
ICandidate extends IPerson
Candidate implements ICandidate
Now if they have chosen not to generate getters/setters then these interfaces just become marker interfaces.
The interface option introduces another case in which Person is generated differently, depending on the contents of other schemas (Should it implement IPerson or not? If it sits alone, it doesn't, if it has others that use it, it does). Whenever we have this situation arise (schemas producing different code depending on the context in which they're used) it creates a lot of confusion.
I am not sure I following this statement.
If I have something like New Starter/onboarding which is a combination of employee and candidate records
{
"id": "http://xxx.xxx.com/schemas/newstarter",
"title": "newstarter",
"type": "object",
"allOf": [
{ "@ref": "http://xxx.xxx.com/schemas/employee" },
{ "@ref": "http://xxx.xxx.com/schemas/candidate" },
],
"properties": {
"newstarterID": {
"description": "identifier of the new starter to an organisation ",
"type": "string"
}
}
}
In this case:
INewStarter extends IEmployee,ICandidate
NewStarter implements INewStarter
My 2 cents after reading the above - I'm still really keen for the interface method. Anecdotally - we have a structure that would really suit polymorphism and separating code around that polymorphism is really valuable. In fact if I were to write by hand I would definitely use that approach.
@neilredway In the example you've given of employee, candidate and newstarter, IEmployee and ICandidate presumably only exist in the case that newstarter references those schemas. If it does not, they don't, and this is what I mean when I mention a type being generated differently based on the content of another schema. Unless IEmployee and ICandidate will always exist? That's a lot of extra cruft that most people don't want or need. This would have to be configurable - it feels like we're overcomplicating this somewhat. This isn't a showstopper, but I think there needs to be some concrete benefits for this extra complexity.
@samskiter It would be useful if you could elaborate a bit on the use-case you have in mind. Another issue that I see here is that this tool will produce a bunch of interfaces but be unable to use them. One might reasonably expect that references to a schema would use its interface, but this isn't possible since it requires polymorphic deserialization. If you're hoping that this will introduce a level of polymorphism into the generated model, unfortunately I don't think this is possible.
I didn't understand the second part of your paragraph unfortunately. To elaborate our use case:
We have a common library project, intended to be used across many projects. Those sub projects define a data type with a json schema and it uses 'allOf' to get some common fields defined in a schema in the parent project. The parent project also does things with some of the common fields.
Effectively the parent library is parameterisable to the specific data type of the child. I'll generate the common type for the parent and the specific type for the child and would like them to inherit so that the parent doesn't have to use the json twice - once to deserialize to it's common type, and once again to expose to the child to deserialize to it's child type and then wouldn't know or be able to help do anything with the child type.
Just going back to this, would you consider a PR that adds allOf support if and only if that allOf is equivalent to "extends" ? i.e. an array of length 2 where the first element is a ref.
Hello,
I am curently only interested in supporting the oneOf
case, so here are my 5 cents regarding oneOf
:
The oneOf
support:
Essentially what we have here is a union type -- the actual value of the property may be any of the given (disjoint?) types.
Given a schema like this (the gist of it at least):
{
"title": "device",
"type": "object",
"properties": {
"name": { "type": "string" },
"warranty": {
"oneOf": [
{ "type": "boolean" },
{ "$ref": "#/definitions/warranty" }
]
}
}
}
in Ceylon language (that has union types built in), I could express this as a class like this:
shared class Device(name, warranty) {
shared String name;
shared Boolean|Warranty warranty;
}
// and use it like this:
Device device = ...;
if (is Warranty w = device.warranty) {
println("The warranty for ``device.name`` expires on ``w.expirationDate``.");
}
else {
println("The device ``device.name`` does not have a warranty");
}
Java does not have union types, so in Java I would have to rely on some intermediary container for oneOf
type values. Something along these lines:
public class Device {
...
private String name;
private DeviceWarranty warranty;
...
public class DeviceWarranty {
private final Object value;
public Boolean toBooelan() {
if (value instanceof Boolean) {
return (Boolean) value;
}
return null;
}
public Warranty toWarranty() {
if (value instanceof Warranty) {
return (Warranty) warranty;
}
return null;
}
}
}
// using it would look roughly like this
Device device = ...;
DeviceWarranty deviceWarranty = device.getWarranty();
Warranty w = deviceWarranty.toWarranty();
if (w != null) {
System.out.println("The warranty for " + device.getName() + " expires on " + w.getExpirationDate() + ".");
}
Are there any updates on the "oneOf" implementation?
+1