Tarmil / FSharp.SystemTextJson

System.Text.Json extensions for F# types
MIT License
323 stars 44 forks source link

using DefaultIgnoreCondition = WhenWritingNull deserializes missing fields as null #154

Closed NinoFloris closed 11 months ago

NinoFloris commented 1 year ago

This issue was in fact not fixed.

Effectively what we want is the following (optionally while also having WhenWritingNull enabled for our C# code):

Since the breaking change the latter just deserializes the empty record "{}" without errors.

SkippableOptionFields does not seem to be what we're after here.

Tarmil commented 1 year ago

So if I understand correctly, a way to tell FSharp.STJ to ignore WhenWritingNull on F# types would do the trick?

Maybe we can add an alternative version of WithSkippableOptionFields that takes an enum instead of a boolean:

type R = { x: int option }

// Equivalent to current default aka .WithSkippableOptionFields(false):
let fsOptions = JsonFSharpOptions().WithSkippableOptionFields(SkippableOptionFields.FromJsonSerializerOptions)
let o1 = fsOptions.ToJsonSerializerOptions()
JsonSerializer.Deserialize<R>("{}", o1) // --> ERROR
let o2 = fsOptions.ToJsonSerializerOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)
JsonSerializer.Deserialize<R>("{}", o2) // --> { x = None }

// Equivalent of current .WithSkippableOptionFields(true):
let fsOptions = JsonFSharpOptions().WithSkippableOptionFields(SkippableOptionFields.Always)
let o1 = fsOptions.ToJsonSerializerOptions()
JsonSerializer.Deserialize<R>("{}", o1) // --> { x = None }
let o2 = fsOptions.ToJsonSerializerOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)
JsonSerializer.Deserialize<R>("{}", o2) // --> { x = None }

// New option that I think would work for you:
let fsOptions = JsonFSharpOptions().WithSkippableOptionFields(SkippableOptionFields.Never)
let o1 = fsOptions.ToJsonSerializerOptions()
JsonSerializer.Deserialize<R>("{}", o1) // --> ERROR
let o2 = fsOptions.ToJsonSerializerOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)
JsonSerializer.Deserialize<R>("{}", o2) // --> ERROR
NinoFloris commented 1 year ago

Thanks for thinking along, yeah the latter seems like what I want.

Have you tried running this part on 1.1.23 though?

type R = { x: int option }

let fsOptions = JsonFSharpOptions().WithSkippableOptionFields(false)
let o1 = fsOptions.ToJsonSerializerOptions()
JsonSerializer.Deserialize<R>("{}", o1) // --> ERROR ## Does not actually error ##

It seems something regressed beyond JsonIgnoreCondition.WhenWritingNull as well

Tarmil commented 1 year ago

That's odd, when I try your exact code, I get this:

System.Text.Json.JsonException: Missing field for record type Program+R: x
NinoFloris commented 1 year ago

Ah I see, it's because we have an alias for option = voption

So the actual problem is the following, tested in a clean project this time.

type R = { x: int voption }

let fsOptions = JsonFSharpOptions().WithSkippableOptionFields(false)
let o1 = fsOptions.ToJsonSerializerOptions()
JsonSerializer.Deserialize<R>("{}", o1) // --> ERROR ## Does not actually error ##
Tarmil commented 1 year ago

Ah indeed, that's not good, it should be an error.