diff --git a/src/FSharp.SystemTextJson/Helpers.fs b/src/FSharp.SystemTextJson/Helpers.fs index f2d7bae..97d00a6 100644 --- a/src/FSharp.SystemTextJson/Helpers.fs +++ b/src/FSharp.SystemTextJson/Helpers.fs @@ -155,6 +155,8 @@ type FieldHelper let nullValue = tryGetNullValue fsOptions ty let isSkippableWrapperType = isSkippableType fsOptions ty + let deserializeNullAsSome = + isSkippableWrapperType && not fsOptions.DeserializeNullAsNone let ignoreNullValues = options.IgnoreNullValues || options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull @@ -197,6 +199,7 @@ type FieldHelper member _.NullValue = nullValue member _.DefaultValue = defaultValue member _.IsSkippableWrapperType = isSkippableWrapperType + member _.DeserializeNullAsSome = deserializeNullAsSome member _.CanBeSkipped = canBeSkipped member _.IgnoreOnWrite = ignoreOnWrite member _.DeserializeType = deserializeType @@ -208,7 +211,7 @@ type FieldHelper member this.IsNullable = this.NullValue.IsNone member this.Deserialize(reader: byref) = - if reader.TokenType = JsonTokenType.Null && not this.IsSkippableWrapperType then + if reader.TokenType = JsonTokenType.Null && not this.DeserializeNullAsSome then match this.NullValue with | ValueSome v -> v | ValueNone -> raise (JsonException this.NullDeserializeError) diff --git a/src/FSharp.SystemTextJson/Options.fs b/src/FSharp.SystemTextJson/Options.fs index 14b1a43..b092da5 100644 --- a/src/FSharp.SystemTextJson/Options.fs +++ b/src/FSharp.SystemTextJson/Options.fs @@ -181,6 +181,7 @@ type internal JsonFSharpOptionsRecord = AllowNullFields: bool IncludeRecordProperties: bool SkippableOptionFields: SkippableOptionFields + DeserializeNullAsNone: bool MapFormat: MapFormat Types: JsonFSharpTypes AllowOverride: bool @@ -215,6 +216,7 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) = AllowNullFields = allowNullFields IncludeRecordProperties = includeRecordProperties SkippableOptionFields = SkippableOptionFields.FromJsonSerializerOptions + DeserializeNullAsNone = false MapFormat = MapFormat.ObjectOrArrayOfPairs Types = types AllowOverride = allowOverride @@ -288,10 +290,15 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) = member _.WithIncludeRecordProperties([] includeRecordProperties) = JsonFSharpOptions({ options with IncludeRecordProperties = includeRecordProperties }) - member _.WithSkippableOptionFields(skippableOptionFields) = + member _.WithSkippableOptionFields + ( + skippableOptionFields, + [] deserializeNullAsNone: bool + ) = JsonFSharpOptions( { options with SkippableOptionFields = skippableOptionFields + DeserializeNullAsNone = deserializeNullAsNone UnionEncoding = if skippableOptionFields = SkippableOptionFields.Always then options.UnionEncoding ||| JsonUnionEncoding.UnwrapOption diff --git a/tests/FSharp.SystemTextJson.Tests/Test.Union.fs b/tests/FSharp.SystemTextJson.Tests/Test.Union.fs index 0795941..3fd8573 100644 --- a/tests/FSharp.SystemTextJson.Tests/Test.Union.fs +++ b/tests/FSharp.SystemTextJson.Tests/Test.Union.fs @@ -1363,6 +1363,25 @@ module NonStruct = ) ) + [] + let ``deserialize skippable option from either null or missing`` () = + let options = + JsonFSharpOptions + .Default() + .WithSkippableOptionFields(SkippableOptionFields.Always, deserializeNullAsNone = true) + .ToJsonSerializerOptions() + + let actual = JsonSerializer.Deserialize<{| x: int option |}>("""{}""", options) + Assert.Equal({| x = None |}, actual) + + let actual = + JsonSerializer.Deserialize<{| x: int option |}>("""{"x":null}""", options) + Assert.Equal({| x = None |}, actual) + + let actual = + JsonSerializer.Deserialize<{| x: int option |}>("""{"x":42}""", options) + Assert.Equal({| x = Some 42 |}, actual) + module Struct = [] @@ -2692,3 +2711,22 @@ module Struct = namedAfterTypesOptionsWithNamingPolicy ) ) + + [] + let ``deserialize skippable option from either null or missing`` () = + let options = + JsonFSharpOptions + .Default() + .WithSkippableOptionFields(SkippableOptionFields.Always, deserializeNullAsNone = true) + .ToJsonSerializerOptions() + + let actual = JsonSerializer.Deserialize<{| x: int voption |}>("""{}""", options) + Assert.Equal({| x = ValueNone |}, actual) + + let actual = + JsonSerializer.Deserialize<{| x: int voption |}>("""{"x":null}""", options) + Assert.Equal({| x = ValueNone |}, actual) + + let actual = + JsonSerializer.Deserialize<{| x: int voption |}>("""{"x":42}""", options) + Assert.Equal({| x = ValueSome 42 |}, actual)