core
core copied to clipboard
array<option<string>> serializes oddly
👋 Hey there,
I'm trying to serialize a nested type of array<option<string>> however it seems to output unexpected json given the docs on the json serializer regarding options.
[<JavaScript>]
type Record4 = { Field4: array<option<string>> }
WebSharper.Json.Serialize { Field4 = [|Some "hello"|] }
// expected {"Field4":["hello"]}
// actual: {"Field4":[{"$":1,"Value":"hello"}]}
WebSharper.Json.Serialize { Field4 = [|None|] }
// expected: {"Field4":[null]}
// actual: {"Field4":[null]}
https://try.websharper.com/snippet/0000ZB
At the moment, indeed, option<'T> only gets rudimentary special treatment: a field with an option type is only output if its corresponding value is a Some, and no other attempt is made to improve the output elsewhere, such as inside composite values.
In addition to this, I would be totally up for dropping the option envelope altogether and just outputting the inner value or null - as you suggested above.
Also, looks like voption needs to be properly supported as well.
@TheAngryByrd @granicz To ensure F# nested option values are fully supported (Some None can be distinguished from a None), the representation for F# options are not erased except when it's unambiguous (record type with option field).
To interop with existing/missing JS values directly, you can use the WebSharper.JavaScript.Optional type (it has cases Defined of x and Undefined). There are functions to convert normal F# unions: Optional.ofOption/toOption
@Jand42 Isn't it always unambiguous to drop the option wrapper if the inside is not another option?
@granicz Actually many types can have null as proper value not just options, that we can't erase. And WS does not know that at runtime, so we can't do generic option operations while keeping same semantics.
I think we should keep erased types separate, and .NET types behavior intact.
We have type-directed serialization and deserialization, so we should always know when a null means a None option or something else.
@granicz null is the representation of None when you have a standalone option value. Representing Some null is more problematic, as we need to differentiate from None. We can't do it for standalone option values without a wrapper.
If the option is on a record/object field, we can indeed erase it, and JSON de/serializer does this: None becomes a missing field (JS get will yield undefined), while Some null becomes a field with null value. No information lost.
In the current example, we would need to embed the option into an Array. There is no good way to do it without loss of information as undefined is not part of JSON standard, neither sparse arrays.
@TheAngryByrd If you don't care about None versus Some null, actually the best way to encode your string option array is to flatten it to string array with |> Array.map (Option.defaultValue null). But I think it's best if WebSharper is not making this conversion implicitly.
Thanks for all the feedback! I ended up with the |> Array.map (Option.defaultValue null) for now.