Skip to content

Commit d36bbaf

Browse files
authored
Merge pull request #202 from Tarmil/override-members
Allow overriding property and union case attributes in options
2 parents 01b26f8 + ac780bc commit d36bbaf

5 files changed

Lines changed: 158 additions & 3 deletions

File tree

src/FSharp.SystemTextJson/Options.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ type internal JsonFSharpOptionsRecord =
185185
MapFormat: MapFormat
186186
Types: JsonFSharpTypes
187187
AllowOverride: bool
188+
OverrideMembers: IDictionary<string, seq<Attribute>>
188189
Overrides: JsonFSharpOptions -> IDictionary<Type, JsonFSharpOptions> }
189190

190191
and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
@@ -224,6 +225,7 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
224225
MapFormat = MapFormat.ObjectOrArrayOfPairs
225226
Types = types
226227
AllowOverride = allowOverride
228+
OverrideMembers = Map.empty
227229
Overrides = emptyOverrides }
228230
)
229231

@@ -340,6 +342,9 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
340342
member _.WithOverrides(overrides) =
341343
JsonFSharpOptions({ options with Overrides = fun _ -> overrides })
342344

345+
member _.WithOverrideMembers(overrides) =
346+
JsonFSharpOptions({ options with OverrideMembers = overrides })
347+
343348
member private this.WithUnionEncodingFlag(flag, set) =
344349
if set then
345350
this.WithUnionEncoding(options.UnionEncoding ||| flag)

src/FSharp.SystemTextJson/Record.fs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ open FSharp.Reflection
88
open System.Text.Json.Serialization.Helpers
99

1010
type private RecordField
11+
private
1112
(
1213
fsOptions: JsonFSharpOptionsRecord,
1314
options: JsonSerializerOptions,
@@ -37,7 +38,18 @@ type private RecordField
3738

3839
new(fsOptions, options: JsonSerializerOptions, i, p: PropertyInfo, fieldOrderIndices) =
3940
let names =
40-
match getJsonNames "field" (fun ty -> p.GetCustomAttributes(ty, true)) with
41+
match
42+
getJsonNames
43+
"field"
44+
(fun ty ->
45+
match fsOptions.OverrideMembers.TryGetValue(p.Name) with
46+
| true, attrs ->
47+
[| for attr in attrs do
48+
if attr.GetType().IsAssignableFrom(ty) then
49+
box attr |]
50+
| false, _ -> p.GetCustomAttributes(ty, true)
51+
)
52+
with
4153
| ValueSome names -> names |> Array.map (fun n -> n.AsString())
4254
| ValueNone -> [| convertName options.PropertyNamingPolicy p.Name |]
4355
RecordField(fsOptions, options, i, p, fieldOrderIndices, names)

src/FSharp.SystemTextJson/Union.fs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,18 @@ module private Case =
7474
(options: JsonSerializerOptions)
7575
(uci: UnionCaseInfo)
7676
=
77+
let getAttrs ty =
78+
match fsOptions.OverrideMembers.TryGetValue(uci.Name) with
79+
| true, attrs ->
80+
[| for attr in attrs do
81+
if attr.GetType().IsAssignableFrom(ty) then
82+
box attr |]
83+
| false, _ -> uci.GetCustomAttributes(ty)
7784
let names =
78-
match getJsonNames "case" uci.GetCustomAttributes with
85+
match getJsonNames "case" getAttrs with
7986
| ValueSome name -> name
8087
| ValueNone -> [| JsonName.String(convertName tagNamingPolicy uci.Name) |]
81-
let fieldNames = getJsonFieldNames uci.GetCustomAttributes
88+
let fieldNames = getJsonFieldNames getAttrs
8289
let fields =
8390
let fields = uci.GetFields()
8491
let usedFieldNames = Dictionary()

tests/FSharp.SystemTextJson.Tests/Test.Record.fs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,26 @@ module NonStruct =
497497
JsonSerializer.Serialize({ incX = 1; incY = "a" }, dontIncludeRecordPropertiesOptions)
498498
Assert.Equal("""{"incX":1,"incY":"a","incZ":42}""", actual)
499499

500+
type OverrideMembersRecord = { x: int; y: string }
501+
502+
let overrideMembersOptions =
503+
JsonFSharpOptions()
504+
.WithOverrides(fun o ->
505+
dict [ typeof<OverrideMembersRecord>, o.WithOverrideMembers(dict [ "x", [ JsonNameAttribute("z") ] ]) ]
506+
)
507+
.ToJsonSerializerOptions()
508+
509+
[<Fact>]
510+
let ``serialize with OverrideMembers`` () =
511+
let actual = JsonSerializer.Serialize({ x = 1; y = "b" }, overrideMembersOptions)
512+
Assert.Equal("""{"z":1,"y":"b"}""", actual)
513+
514+
[<Fact>]
515+
let ``deserialize with OverrideMembers`` () =
516+
let actual =
517+
JsonSerializer.Deserialize<OverrideMembersRecord>("""{"z":1,"y":"b"}""", overrideMembersOptions)
518+
Assert.Equal({ x = 1; y = "b" }, actual)
519+
500520
module Struct =
501521

502522
[<Struct; JsonFSharpConverter>]
@@ -943,3 +963,24 @@ module Struct =
943963
let actual =
944964
JsonSerializer.Serialize({ incX = 1; incY = "a" }, dontIncludeRecordPropertiesOptions)
945965
Assert.Equal("""{"incX":1,"incY":"a","incZ":42}""", actual)
966+
967+
[<Struct>]
968+
type OverrideMembersRecord = { x: int; y: string }
969+
970+
let overrideMembersOptions =
971+
JsonFSharpOptions()
972+
.WithOverrides(fun o ->
973+
dict [ typeof<OverrideMembersRecord>, o.WithOverrideMembers(dict [ "x", [ JsonNameAttribute("z") ] ]) ]
974+
)
975+
.ToJsonSerializerOptions()
976+
977+
[<Fact>]
978+
let ``serialize with OverrideMembers`` () =
979+
let actual = JsonSerializer.Serialize({ x = 1; y = "b" }, overrideMembersOptions)
980+
Assert.Equal("""{"z":1,"y":"b"}""", actual)
981+
982+
[<Fact>]
983+
let ``deserialize with OverrideMembers`` () =
984+
let actual =
985+
JsonSerializer.Deserialize<OverrideMembersRecord>("""{"z":1,"y":"b"}""", overrideMembersOptions)
986+
Assert.Equal({ x = 1; y = "b" }, actual)

tests/FSharp.SystemTextJson.Tests/Test.Union.fs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,47 @@ module NonStruct =
14291429
let actual = JsonSerializer.Deserialize("""{"enum-a":1}""", options)
14301430
Assert.Equal<Map<_, _>>(Map [ EnumA, 1 ], actual)
14311431

1432+
let overrideCasesOptions =
1433+
JsonFSharpOptions
1434+
.Default()
1435+
.WithOverrides(fun o ->
1436+
dict
1437+
[ typedefof<Result<_, _>>,
1438+
o
1439+
.WithUnionInternalTag()
1440+
.WithUnionNamedFields()
1441+
.WithUnionTagName("isSuccess")
1442+
.WithOverrideMembers(
1443+
dict
1444+
[ nameof Ok,
1445+
[ JsonNameAttribute(true)
1446+
JsonNameAttribute("value", Field = "ResultValue") ]
1447+
nameof Error,
1448+
[ JsonNameAttribute(false)
1449+
JsonNameAttribute("error", Field = "ErrorValue") ] ]
1450+
) ]
1451+
)
1452+
.ToJsonSerializerOptions()
1453+
1454+
[<Fact>]
1455+
let ``serialize with OverrideMembers`` () =
1456+
let actual = JsonSerializer.Serialize(Ok 42, overrideCasesOptions)
1457+
Assert.Equal("""{"isSuccess":true,"value":42}""", actual)
1458+
let actual = JsonSerializer.Serialize(Error "failed :(", overrideCasesOptions)
1459+
Assert.Equal("""{"isSuccess":false,"error":"failed :("}""", actual)
1460+
1461+
[<Fact>]
1462+
let ``deserialize with OverrideMembers`` () =
1463+
let actual =
1464+
JsonSerializer.Deserialize<Result<int, string>>("""{"isSuccess":true,"value":42}""", overrideCasesOptions)
1465+
Assert.Equal(Ok 42, actual)
1466+
let actual =
1467+
JsonSerializer.Deserialize<Result<int, string>>(
1468+
"""{"isSuccess":false,"error":"failed :("}""",
1469+
overrideCasesOptions
1470+
)
1471+
Assert.Equal(Error "failed :(", actual)
1472+
14321473
module Struct =
14331474

14341475
[<Struct; JsonFSharpConverter>]
@@ -2789,3 +2830,52 @@ module Struct =
27892830
enumLikeOptions().WithUnionTagNamingPolicy(JsonNamingPolicy.KebabCaseLower).ToJsonSerializerOptions()
27902831
let actual = JsonSerializer.Deserialize("""{"enum-a":1}""", options)
27912832
Assert.Equal<Map<_, _>>(Map [ EnumA, 1 ], actual)
2833+
2834+
[<Struct>]
2835+
type CustomResult<'TOk, 'TError> =
2836+
| Ok of ResultValue: 'TOk
2837+
| Error of ErrorValue: 'TError
2838+
2839+
let overrideCasesOptions =
2840+
JsonFSharpOptions
2841+
.Default()
2842+
.WithOverrides(fun o ->
2843+
dict
2844+
[ typedefof<CustomResult<_, _>>,
2845+
o
2846+
.WithUnionInternalTag()
2847+
.WithUnionNamedFields()
2848+
.WithUnionTagName("isSuccess")
2849+
.WithOverrideMembers(
2850+
dict
2851+
[ nameof Ok,
2852+
[ JsonNameAttribute(true)
2853+
JsonNameAttribute("value", Field = "ResultValue") ]
2854+
nameof Error,
2855+
[ JsonNameAttribute(false)
2856+
JsonNameAttribute("error", Field = "ErrorValue") ] ]
2857+
) ]
2858+
)
2859+
.ToJsonSerializerOptions()
2860+
2861+
[<Fact>]
2862+
let ``serialize with OverrideMembers`` () =
2863+
let actual = JsonSerializer.Serialize(Ok 42, overrideCasesOptions)
2864+
Assert.Equal("""{"isSuccess":true,"value":42}""", actual)
2865+
let actual = JsonSerializer.Serialize(Error "failed :(", overrideCasesOptions)
2866+
Assert.Equal("""{"isSuccess":false,"error":"failed :("}""", actual)
2867+
2868+
[<Fact>]
2869+
let ``deserialize with OverrideMembers`` () =
2870+
let actual =
2871+
JsonSerializer.Deserialize<CustomResult<int, string>>(
2872+
"""{"isSuccess":true,"value":42}""",
2873+
overrideCasesOptions
2874+
)
2875+
Assert.Equal(Ok 42, actual)
2876+
let actual =
2877+
JsonSerializer.Deserialize<CustomResult<int, string>>(
2878+
"""{"isSuccess":false,"error":"failed :("}""",
2879+
overrideCasesOptions
2880+
)
2881+
Assert.Equal(Error "failed :(", actual)

0 commit comments

Comments
 (0)