-
Notifications
You must be signed in to change notification settings - Fork 3
Added support for boolean combination keywords #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for boolean combination keywords #7
Conversation
This would be really nice to have, I've got some local hacks to do similar for How does this integrate with |
Good question! I think the best bet would be to add allOf, etc to StructTypes.excludes, avoiding writing and reading the element at all. You only really want it to show up in the generated schema. |
Ah now I realise you perhaps mean deserializing JSON that has these keywords into automatically generated types that use these AllOf{T,S} etc structs? That's not something I've really thought about, although I would guess it would be fairly straightforward? But my use case was only for the reverse problem (types->schema) for now! |
Yes, that's my main use case. I want to be able to define some Julia structs, probably nested several layers, and generate a JSON schema that when you happen to generate a valid JSON object for that schema you can directly call |
I agree the full StructTypes integration would be optimal. Or at least make sure we don't block that path forward for a next PR (does it right now?). I'd also love to see maybe an example string of such a schema. I always forget how JSON schemas actually work :) Nice to add to the readme maybe too if it's not too large. And given the growing readme we should consider making proper docs. But I can make that a separate issue of it's too much work. |
Ah well that's indeed possible here! That's what we're doing with it. I've made an update to enforce adding any extra 'keyword' properties of structs to StructTypes.excludes so they definitely don't get in the way of deserializing. What I thought you meant, and is not possible (yet, and I'm not sure is really a good idea) is to generate the types themselves automatically in the sense of https://quinnj.github.io/JSON3.jl/stable/#Generated-Types.
Good idea, will do that tomorrow! |
Added the JSON string to the readme! One more note on (de)serializing: the AllOf etc structs don't get in the way, but using Val to get const values in the schema definitely does not work with JSON3. They should only be used for these kinds of validation purposes. |
Maybe constant values with Given this issue, I would prefer to separate the Also please version bump to v0.3.0, then I can directly register after this PR is merged. |
Edit: wait your example works fine right? The But do please version bump :) |
…module aliasing using as in tests
Indeed, have added a quick note to the readme that this usage of Val should only really be used for validation purposes.
For reference, the use case I have is that we have a struct containing some feature toggle alongside settings for that feature. These settings should be optional if the toggle is false, but become required if the toggle is true. This can be achieved using anyOf [ schema where feature toggle is off (using toggle : { const : false } ) , schema where feature settings are required ]. (Refactoring could also achieve this without needing anyOf, but unfortunately the flat format where the toggle and settings sit at the same level is fairly set in stone already!)
Done! |
Also I'm still wondering if we should make this a trait/function instead of a field? Have you considered that? So instead of this: struct BooleanCombinationSchema
int::Int
bool::Bool
allOf::JSG.AllOf{
JSG.AnyOf{ConstantInt1Schema, ConstantInt2Schema},
JSG.Not{ConstantBoolTrueSchema}
}
end
StructTypes.StructType(::Type{BooleanCombinationSchema}) = StructTypes.Struct()
StructTypes.excludes(::Type{BooleanCombinationSchema}) = (:allOf,) What if we did this? struct BooleanCombinationSchema
int::Int
bool::Bool
end
StructTypes.StructType(::Type{BooleanCombinationSchema}) = StructTypes.Struct()
function JSG.combination(::Type{BooleanCombinationSchema}) =
return JSG.AllOf{
JSG.AnyOf{ConstantInt1Schema, ConstantInt2Schema},
JSG.Not{ConstantBoolTrueSchema}
}
end Now it's clear this is never ever a field that will get (de)serialized by JSON3 struct types, it's a schema thing, like the |
Oh I like that! I'll give it a try and see how it goes I did it with fields so the julia struct more closely matches the JSON schema you get out, but it introduces the fiddly need to add to excludes and potentially define a new constructor to hide the keyword fields away. |
Much nicer for the user and also made the implementation cleaner, good idea! Could you please rerun the github tests, thanks |
Yeah I like it the more I think about it. This way people can also generate their schema from existing structs if they want, without importing JSONSchemaGenerator into their code and editing those structs. |
In principle we only need to specify the types we want to combine and the fact that we want the anyOf keyword. In fact, now I think about it, this trait-based approach also lets us solve the problem of providing an arbitrary number of schemas to combine (instead of the current 2) by instead providing an object that has an array of types to combine. Could even do something very generic to provide keywords along the lines of struct ArrayKeyword # for making things like anyOf : []
string keyword
Type[] subschemas
end
struct SingletonKeyword # for making things like not : {}, or maybe even const : {}
string keyword
Type subschema
end
JSG.keywords(::Type)::Union{SingletonKeyword, ArrayKeyword}[] But for now I think the specific AnyOf, etc types make it clearer how to use them and more easily avoid incorrect schemas, |
src/JSONSchemaGenerator.jl
Outdated
struct Not{T} end | ||
|
||
# add use of the above keywords to a struct by specifying a list of them to use with combinationkeywords | ||
combinationkeywords(::Type) = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make the default a tuple ()
, it's generally more optimal than using an array (can be compiled away I think, while arrays cannot)
Maybe we should also add a docstring and export this function, since it's now a formal interface.
The same holds for AllOf
, AnyOf
, OneOf
and Not
(although Not
may conflict with InvertedIndices.Not)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good one, what do you think of calling them JSONNot, JSONAllOf, etc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, no that feels ugly :)
We can also choose just not to export for now, or accept a possible clash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually StructTypes.jl also doesn't export anything, but it does document all the 'public' types and functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can add some docstrings then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also choose just not to export for now
I think this is fine. Then using these types and functions helps make it clear what they are doing, i.e. something to do with schema generation
Looking into Tuples made me realise that we can implement validation against enums as well as consts. Have updated the example and tests. The point here that differentiates this use from a properly defined (and serializable) enum is that we can include things like numbers or reserved keywords like true/false. |
Well everything looks good to me, let's merge and I'll register afterwards. |
Was wanting to use the JSON boolean combination keywords (https://json-schema.org/understanding-json-schema/reference/combining#boolean-json-schema-combination) in a schema but still have the schema automatically generated.
Main changes:
Included an extra test making use of these changes.
Let me know if you think there's a nicer way to do this!