Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/FSharp.SystemTextJson/Options.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ type internal JsonFSharpOptionsRecord =
MapFormat: MapFormat
Types: JsonFSharpTypes
AllowOverride: bool
OverrideMembers: IDictionary<string, seq<Attribute>>
Overrides: JsonFSharpOptions -> IDictionary<Type, JsonFSharpOptions> }

and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
Expand Down Expand Up @@ -224,6 +225,7 @@ and JsonFSharpOptions internal (options: JsonFSharpOptionsRecord) =
MapFormat = MapFormat.ObjectOrArrayOfPairs
Types = types
AllowOverride = allowOverride
OverrideMembers = Map.empty
Overrides = emptyOverrides }
)

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

member _.WithOverrideMembers(overrides) =
JsonFSharpOptions({ options with OverrideMembers = overrides })

member private this.WithUnionEncodingFlag(flag, set) =
if set then
this.WithUnionEncoding(options.UnionEncoding ||| flag)
Expand Down
14 changes: 13 additions & 1 deletion src/FSharp.SystemTextJson/Record.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ open FSharp.Reflection
open System.Text.Json.Serialization.Helpers

type private RecordField
private
(
fsOptions: JsonFSharpOptionsRecord,
options: JsonSerializerOptions,
Expand Down Expand Up @@ -37,7 +38,18 @@ type private RecordField

new(fsOptions, options: JsonSerializerOptions, i, p: PropertyInfo, fieldOrderIndices) =
let names =
match getJsonNames "field" (fun ty -> p.GetCustomAttributes(ty, true)) with
match
getJsonNames
"field"
(fun ty ->
match fsOptions.OverrideMembers.TryGetValue(p.Name) with
| true, attrs ->
[| for attr in attrs do
if attr.GetType().IsAssignableFrom(ty) then
box attr |]
| false, _ -> p.GetCustomAttributes(ty, true)
)
with
| ValueSome names -> names |> Array.map (fun n -> n.AsString())
| ValueNone -> [| convertName options.PropertyNamingPolicy p.Name |]
RecordField(fsOptions, options, i, p, fieldOrderIndices, names)
Expand Down
11 changes: 9 additions & 2 deletions src/FSharp.SystemTextJson/Union.fs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,18 @@ module private Case =
(options: JsonSerializerOptions)
(uci: UnionCaseInfo)
=
let getAttrs ty =
match fsOptions.OverrideMembers.TryGetValue(uci.Name) with
| true, attrs ->
[| for attr in attrs do
if attr.GetType().IsAssignableFrom(ty) then
box attr |]
| false, _ -> uci.GetCustomAttributes(ty)
let names =
match getJsonNames "case" uci.GetCustomAttributes with
match getJsonNames "case" getAttrs with
| ValueSome name -> name
| ValueNone -> [| JsonName.String(convertName tagNamingPolicy uci.Name) |]
let fieldNames = getJsonFieldNames uci.GetCustomAttributes
let fieldNames = getJsonFieldNames getAttrs
let fields =
let fields = uci.GetFields()
let usedFieldNames = Dictionary()
Expand Down
41 changes: 41 additions & 0 deletions tests/FSharp.SystemTextJson.Tests/Test.Record.fs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,26 @@ module NonStruct =
JsonSerializer.Serialize({ incX = 1; incY = "a" }, dontIncludeRecordPropertiesOptions)
Assert.Equal("""{"incX":1,"incY":"a","incZ":42}""", actual)

type OverrideMembersRecord = { x: int; y: string }

let overrideMembersOptions =
JsonFSharpOptions()
.WithOverrides(fun o ->
dict [ typeof<OverrideMembersRecord>, o.WithOverrideMembers(dict [ "x", [ JsonNameAttribute("z") ] ]) ]
)
.ToJsonSerializerOptions()

[<Fact>]
let ``serialize with OverrideMembers`` () =
let actual = JsonSerializer.Serialize({ x = 1; y = "b" }, overrideMembersOptions)
Assert.Equal("""{"z":1,"y":"b"}""", actual)

[<Fact>]
let ``deserialize with OverrideMembers`` () =
let actual =
JsonSerializer.Deserialize<OverrideMembersRecord>("""{"z":1,"y":"b"}""", overrideMembersOptions)
Assert.Equal({ x = 1; y = "b" }, actual)

module Struct =

[<Struct; JsonFSharpConverter>]
Expand Down Expand Up @@ -943,3 +963,24 @@ module Struct =
let actual =
JsonSerializer.Serialize({ incX = 1; incY = "a" }, dontIncludeRecordPropertiesOptions)
Assert.Equal("""{"incX":1,"incY":"a","incZ":42}""", actual)

[<Struct>]
type OverrideMembersRecord = { x: int; y: string }

let overrideMembersOptions =
JsonFSharpOptions()
.WithOverrides(fun o ->
dict [ typeof<OverrideMembersRecord>, o.WithOverrideMembers(dict [ "x", [ JsonNameAttribute("z") ] ]) ]
)
.ToJsonSerializerOptions()

[<Fact>]
let ``serialize with OverrideMembers`` () =
let actual = JsonSerializer.Serialize({ x = 1; y = "b" }, overrideMembersOptions)
Assert.Equal("""{"z":1,"y":"b"}""", actual)

[<Fact>]
let ``deserialize with OverrideMembers`` () =
let actual =
JsonSerializer.Deserialize<OverrideMembersRecord>("""{"z":1,"y":"b"}""", overrideMembersOptions)
Assert.Equal({ x = 1; y = "b" }, actual)
90 changes: 90 additions & 0 deletions tests/FSharp.SystemTextJson.Tests/Test.Union.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,47 @@ module NonStruct =
let actual = JsonSerializer.Deserialize("""{"enum-a":1}""", options)
Assert.Equal<Map<_, _>>(Map [ EnumA, 1 ], actual)

let overrideCasesOptions =
JsonFSharpOptions
.Default()
.WithOverrides(fun o ->
dict
[ typedefof<Result<_, _>>,
o
.WithUnionInternalTag()
.WithUnionNamedFields()
.WithUnionTagName("isSuccess")
.WithOverrideMembers(
dict
[ nameof Ok,
[ JsonNameAttribute(true)
JsonNameAttribute("value", Field = "ResultValue") ]
nameof Error,
[ JsonNameAttribute(false)
JsonNameAttribute("error", Field = "ErrorValue") ] ]
) ]
)
.ToJsonSerializerOptions()

[<Fact>]
let ``serialize with OverrideMembers`` () =
let actual = JsonSerializer.Serialize(Ok 42, overrideCasesOptions)
Assert.Equal("""{"isSuccess":true,"value":42}""", actual)
let actual = JsonSerializer.Serialize(Error "failed :(", overrideCasesOptions)
Assert.Equal("""{"isSuccess":false,"error":"failed :("}""", actual)

[<Fact>]
let ``deserialize with OverrideMembers`` () =
let actual =
JsonSerializer.Deserialize<Result<int, string>>("""{"isSuccess":true,"value":42}""", overrideCasesOptions)
Assert.Equal(Ok 42, actual)
let actual =
JsonSerializer.Deserialize<Result<int, string>>(
"""{"isSuccess":false,"error":"failed :("}""",
overrideCasesOptions
)
Assert.Equal(Error "failed :(", actual)

module Struct =

[<Struct; JsonFSharpConverter>]
Expand Down Expand Up @@ -2789,3 +2830,52 @@ module Struct =
enumLikeOptions().WithUnionTagNamingPolicy(JsonNamingPolicy.KebabCaseLower).ToJsonSerializerOptions()
let actual = JsonSerializer.Deserialize("""{"enum-a":1}""", options)
Assert.Equal<Map<_, _>>(Map [ EnumA, 1 ], actual)

[<Struct>]
type CustomResult<'TOk, 'TError> =
| Ok of ResultValue: 'TOk
| Error of ErrorValue: 'TError

let overrideCasesOptions =
JsonFSharpOptions
.Default()
.WithOverrides(fun o ->
dict
[ typedefof<CustomResult<_, _>>,
o
.WithUnionInternalTag()
.WithUnionNamedFields()
.WithUnionTagName("isSuccess")
.WithOverrideMembers(
dict
[ nameof Ok,
[ JsonNameAttribute(true)
JsonNameAttribute("value", Field = "ResultValue") ]
nameof Error,
[ JsonNameAttribute(false)
JsonNameAttribute("error", Field = "ErrorValue") ] ]
) ]
)
.ToJsonSerializerOptions()

[<Fact>]
let ``serialize with OverrideMembers`` () =
let actual = JsonSerializer.Serialize(Ok 42, overrideCasesOptions)
Assert.Equal("""{"isSuccess":true,"value":42}""", actual)
let actual = JsonSerializer.Serialize(Error "failed :(", overrideCasesOptions)
Assert.Equal("""{"isSuccess":false,"error":"failed :("}""", actual)

[<Fact>]
let ``deserialize with OverrideMembers`` () =
let actual =
JsonSerializer.Deserialize<CustomResult<int, string>>(
"""{"isSuccess":true,"value":42}""",
overrideCasesOptions
)
Assert.Equal(Ok 42, actual)
let actual =
JsonSerializer.Deserialize<CustomResult<int, string>>(
"""{"isSuccess":false,"error":"failed :("}""",
overrideCasesOptions
)
Assert.Equal(Error "failed :(", actual)