Skip to content

Commit

Permalink
feat: Allow using single-case unions as dictionary keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmil committed Aug 2, 2023
1 parent a233a92 commit 0fdb2fd
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 3 deletions.
20 changes: 17 additions & 3 deletions src/FSharp.SystemTextJson/Union.fs
Original file line number Diff line number Diff line change
Expand Up @@ -702,18 +702,32 @@ type JsonUnwrapValueOptionConverter<'T>() =
| ValueNone -> writer.WriteNullValue()
| ValueSome x -> JsonSerializer.Serialize<'T>(writer, x, options)

type JsonUnwrappedUnionConverter<'T, 'FieldT>(case: UnionCaseInfo) =
type JsonUnwrappedUnionConverter<'T, 'FieldT>(case: UnionCaseInfo, options: JsonSerializerOptions) =
inherit JsonConverter<'T>()

let ctor = FSharpValue.PreComputeUnionConstructor(case, true)
let getter = FSharpValue.PreComputeUnionReader(case, true)
let innerConverter =
match options.GetConverter(typeof<'FieldT>) with
| :? JsonConverter<'FieldT> as c -> c
| _ -> null

override _.Read(reader, _typeToConvert, options) =
ctor [| box (JsonSerializer.Deserialize<'FieldT>(&reader, options)) |] :?> 'T

override _.ReadAsPropertyName(reader, typeToConvert, options) =
match innerConverter with
| null -> base.ReadAsPropertyName(&reader, typeToConvert, options)
| innerConverter -> ctor [| innerConverter.ReadAsPropertyName(&reader, typeof<'FieldT>, options) |] :?> 'T

override _.Write(writer, value, options) =
JsonSerializer.Serialize<'FieldT>(writer, (getter value)[0] :?> 'FieldT, options)

override _.WriteAsPropertyName(writer, value, options) =
match innerConverter with
| null -> base.WriteAsPropertyName(writer, value, options)
| innerConverter -> innerConverter.WriteAsPropertyName(writer, (getter value)[0] :?> 'FieldT, options)

type JsonUnionConverter(fsOptions: JsonFSharpOptions) =
inherit JsonConverterFactory()

Expand Down Expand Up @@ -769,8 +783,8 @@ type JsonUnionConverter(fsOptions: JsonFSharpOptions) =
let case = cases[0]
jsonUnwrappedUnionConverterTy
.MakeGenericType([| typeToConvert; unwrappedSingleCaseField.PropertyType |])
.GetConstructor([| caseTy |])
.Invoke([| case |])
.GetConstructor([| caseTy; typeof<JsonSerializerOptions> |])
.Invoke([| case; options |])
:?> JsonConverter
| false, cases, _ ->
jsonUnionConverterTy
Expand Down
32 changes: 32 additions & 0 deletions tests/FSharp.SystemTextJson.Tests/Test.Collection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ let ``serialize newtype-string-keyed map`` (m: Map<NonNull<string>, int>) =
let actual = JsonSerializer.Serialize(m, options)
Assert.Equal(expected, actual)

[<Property>]
let ``deserialize newtype-string-keyed dictionary`` (d': Dictionary<NonNull<string>, int>) =
let d = Dictionary()
for KeyValue(NonNull k, v) in d' do d[UserId k] <- v
let ser = "{" + String.concat "," (Seq.map serKV1_1 d) + "}"
let actual = JsonSerializer.Deserialize<Dictionary<UserId, int>>(ser, options)
Assert.Equal<Dictionary<UserId, int>>(d, actual)

[<Property>]
let ``serialize newtype-string-keyed dictionary`` (d': Dictionary<NonNull<string>, int>) =
let d = Dictionary()
for KeyValue(NonNull k, v) in d' do d[UserId k] <- v
let expected = "{" + String.concat "," (Seq.map serKV1_1 d) + "}"
let actual = JsonSerializer.Serialize(d, options)
Assert.Equal(expected, actual)

[<Struct>]
type SUserId = SUserId of string

Expand All @@ -118,6 +134,22 @@ let ``serialize struct-newtype-string-keyed map`` (m: Map<NonNull<string>, int>)
let actual = JsonSerializer.Serialize(m, options)
Assert.Equal(expected, actual)

[<Property>]
let ``deserialize struct-newtype-string-keyed dictionary`` (d': Dictionary<NonNull<string>, int>) =
let d = Dictionary()
for KeyValue(NonNull k, v) in d' do d[SUserId k] <- v
let ser = "{" + String.concat "," (Seq.map serKV1_2 d) + "}"
let actual = JsonSerializer.Deserialize<Dictionary<SUserId, int>>(ser, options)
Assert.Equal<Dictionary<SUserId, int>>(d, actual)

[<Property>]
let ``serialize struct-newtype-string-keyed dictionary`` (d': Dictionary<NonNull<string>, int>) =
let d = Dictionary()
for KeyValue(NonNull k, v) in d' do d[SUserId k] <- v
let expected = "{" + String.concat "," (Seq.map serKV1_2 d) + "}"
let actual = JsonSerializer.Serialize(d, options)
Assert.Equal(expected, actual)

let keyPolicyOptions =
JsonFSharpOptions()
.ToJsonSerializerOptions(DictionaryKeyPolicy = JsonNamingPolicy.CamelCase)
Expand Down

0 comments on commit 0fdb2fd

Please sign in to comment.