Skip to content

Commit

Permalink
Merge pull request #184 from Tarmil/deserialize-null-as-none
Browse files Browse the repository at this point in the history
[#151] Add option to deserialize both null and missing as None
  • Loading branch information
Tarmil authored Apr 4, 2024
2 parents 1b6dbd9 + f47c39d commit 498f7a5
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/FSharp.SystemTextJson/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -208,7 +211,7 @@ type FieldHelper
member this.IsNullable = this.NullValue.IsNone

member this.Deserialize(reader: byref<Utf8JsonReader>) =
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)
Expand Down
9 changes: 8 additions & 1 deletion src/FSharp.SystemTextJson/Options.fs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type internal JsonFSharpOptionsRecord =
AllowNullFields: bool
IncludeRecordProperties: bool
SkippableOptionFields: SkippableOptionFields
DeserializeNullAsNone: bool
MapFormat: MapFormat
Types: JsonFSharpTypes
AllowOverride: bool
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -288,10 +290,15 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
member _.WithIncludeRecordProperties([<Optional; DefaultParameterValue true>] includeRecordProperties) =
JsonFSharpOptions({ options with IncludeRecordProperties = includeRecordProperties })

member _.WithSkippableOptionFields(skippableOptionFields) =
member _.WithSkippableOptionFields
(
skippableOptionFields,
[<Optional; DefaultParameterValue false>] deserializeNullAsNone: bool
) =
JsonFSharpOptions(
{ options with
SkippableOptionFields = skippableOptionFields
DeserializeNullAsNone = deserializeNullAsNone
UnionEncoding =
if skippableOptionFields = SkippableOptionFields.Always then
options.UnionEncoding ||| JsonUnionEncoding.UnwrapOption
Expand Down
38 changes: 38 additions & 0 deletions tests/FSharp.SystemTextJson.Tests/Test.Union.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,25 @@ module NonStruct =
)
)

[<Fact>]
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 =

[<Struct; JsonFSharpConverter>]
Expand Down Expand Up @@ -2692,3 +2711,22 @@ module Struct =
namedAfterTypesOptionsWithNamingPolicy
)
)

[<Fact>]
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)

0 comments on commit 498f7a5

Please sign in to comment.