fsprojects / FSharp.Data.JsonSchema

MIT License
48 stars 6 forks source link

Option types do not generate correct schema. #10

Closed faldor20 closed 3 years ago

faldor20 commented 3 years ago

This code:

type inner={
    inner1:int
    inner2:string
}
type outer={
    outer1:inner option
}
let gen=FSharp.Data.JsonSchema.Generator.CreateMemoized ("out")
let b=gen (typeof<outer>)
System.IO.File.WriteAllText("Schema.json",b.ToJson());

Produces the following, which is useless:

Click to expand ```json { "$schema": "http://json-schema.org/draft-04/schema#", "title": "outer", "type": "object", "additionalProperties": false, "properties": { "outer1": { "oneOf": [ { "type": "null" }, { "$ref": "#/definitions/FSharpOptionOfinner" } ] } }, "definitions": { "FSharpOptionOfinner": { "type": [ "null", "object" ], "description": "The type of optional values. When used from other CLI languages the\n empty option is the null value. ", "additionalProperties": false } } } ```

When it should probably be something like:

Click to expand ```json { "$schema": "http://json-schema.org/draft-04/schema#", "title": "outer", "type": "object", "additionalProperties": false, "properties": { "outer1": { "oneOf": [ { "type": "null" }, { "$ref": "#/definitions/FSharpOptionOfinner" } ] } }, "definitions": { "FSharpOptionOfinner": { "properties": { "inner1": { "type": "integer", "format": "int32" }, "inner2": { "type": [ "null", "string" ] }}, "description": "The type of optional values. When used from other CLI languages the\n empty option is the null value. ", "additionalProperties": false } } } ```

Are options just not supported? Or am i maybe doing something wrong?

panesofglass commented 3 years ago

Thanks for the report. I thought I had tested for this, but I will add more and try to resolve.

faldor20 commented 3 years ago

No worries. I actually realized it also happens with unions that contain records and options Here is a little torture test:

type Apple={
    Seeds:int
    Bitten:bool
}
type Food=
|Apple of Apple
|MaybeApple of Apple option
type Misc={
    Amount:int
    Name:string
}
type Basket={
    Misc:Misc option
    Food:Food
}

let gen=FSharp.Data.JsonSchema.Generator.CreateMemoized ("out")
let writeSchema (schema:NJsonSchema.JsonSchema) path= File.WriteAllText(path,schema.ToJson())

let food= Apple{Seeds=10;Bitten=true;}
let misc={Amount=2;Name="Clothes and shoes";}
let basket= {Misc=Some misc; Food=food;}
let basketJson=System.Text.Json.JsonSerializer.Serialize(basket)
File.WriteAllText ("./basket.json",basketJson)

let foodSchema=gen (typeof<Food>)
let miscSchema=gen (typeof<Misc>)
let basketSchema=gen (typeof<Basket>)
writeSchema foodSchema "./foodSchema.json"
writeSchema miscSchema "./miscSchema.json"
writeSchema basketSchema "./basketSchema.json"

The problem is that system.text.json doesn't actually serialize this correctly anyway. It Doesn't actually contain any of the data inside food, just what case it is. I have started using Thoth.json and its autoconverters because it serializes unions and options correctly, and, (just as importantly) in a way that makes some sense to the person looking at the json.

It no longer really matters to me because I'm using Thoth.Json and the schemas aren't compatible (every library seems to have it's own ideas about how to serialize unions).

I'll just hand write my schemas, or just not bother, it was only to assist in manual editing, so its not super important.

Still, thanks for the cool library, happy I could catch this little edge case :)

panesofglass commented 3 years ago

Thank you for the test. I will add this into the library.

panesofglass commented 3 years ago

Field with option type is the issue here. option types work when they are top-level, but the inner level is broken for some reason.

panesofglass commented 3 years ago

Addressing this may involve resolving #9.

panesofglass commented 3 years ago

@faldor20, do the recent changes address the issues you raised? What else should I do to fix the results?