Skip to content

Commit 0a57208

Browse files
authored
Merge pull request #73 from Tarmil/features/overrides
Fix #71: add explicit overrides to JsonFSharpConverter
2 parents 5283b79 + 32eeaec commit 0a57208

7 files changed

Lines changed: 105 additions & 30 deletions

File tree

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ jobs:
2828
2929
- name: Build
3030
run: |
31-
git checkout "$(echo ${{github.ref}} | sed -E 's|^refs/heads/||')"
31+
if echo "${{github.ref}}" | grep -q "^refs/heads/"; then
32+
git checkout "$(echo ${{github.ref}} | sed -E 's|^refs/heads/||')";
33+
fi
3234
./build.sh
3335
3436
- name: Upload nupkg

src/FSharp.SystemTextJson/All.fs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
namespace System.Text.Json.Serialization
22

33
open System
4+
open System.Collections.Generic
45
open System.Runtime.InteropServices
56
open System.Text.Json
67

7-
type JsonFSharpConverter(fsOptions: JsonFSharpOptions) =
8+
type JsonFSharpConverter
9+
(
10+
fsOptions: JsonFSharpOptions,
11+
[<Optional>]
12+
overrides: IDictionary<Type, JsonFSharpOptions>
13+
) =
814
inherit JsonConverterFactory()
915

1016
override _.CanConvert(typeToConvert) =
1117
TypeCache.getKind typeToConvert <> TypeCache.TypeKind.Other
1218

13-
static member internal CreateConverter(typeToConvert, options, fsOptions) =
19+
static member internal CreateConverter(typeToConvert, options, fsOptions, overrides) =
1420
match TypeCache.getKind typeToConvert with
1521
| TypeCache.TypeKind.List ->
1622
JsonListConverter.CreateConverter(typeToConvert, fsOptions)
@@ -21,14 +27,14 @@ type JsonFSharpConverter(fsOptions: JsonFSharpOptions) =
2127
| TypeCache.TypeKind.Tuple ->
2228
JsonTupleConverter.CreateConverter(typeToConvert, fsOptions)
2329
| TypeCache.TypeKind.Record ->
24-
JsonRecordConverter.CreateConverter(typeToConvert, options, fsOptions)
30+
JsonRecordConverter.CreateConverter(typeToConvert, options, fsOptions, overrides)
2531
| TypeCache.TypeKind.Union ->
26-
JsonUnionConverter.CreateConverter(typeToConvert, options, fsOptions)
32+
JsonUnionConverter.CreateConverter(typeToConvert, options, fsOptions, overrides)
2733
| _ ->
2834
invalidOp ("Not an F# record or union type: " + typeToConvert.FullName)
2935

3036
override _.CreateConverter(typeToConvert, options) =
31-
JsonFSharpConverter.CreateConverter(typeToConvert, options, fsOptions)
37+
JsonFSharpConverter.CreateConverter(typeToConvert, options, fsOptions, overrides)
3238

3339
new (
3440
[<Optional; DefaultParameterValue(Default.UnionEncoding)>]
@@ -44,9 +50,11 @@ type JsonFSharpConverter(fsOptions: JsonFSharpOptions) =
4450
[<Optional; DefaultParameterValue(Default.AllowNullFields)>]
4551
allowNullFields: bool,
4652
[<Optional; DefaultParameterValue(false)>]
47-
allowOverride: bool
53+
allowOverride: bool,
54+
[<Optional>]
55+
overrides: IDictionary<Type, JsonFSharpOptions>
4856
) =
49-
JsonFSharpConverter(JsonFSharpOptions(unionEncoding, unionTagName, unionFieldsName, unionTagNamingPolicy, unionTagCaseInsensitive, allowNullFields, allowOverride))
57+
JsonFSharpConverter(JsonFSharpOptions(unionEncoding, unionTagName, unionFieldsName, unionTagNamingPolicy, unionTagCaseInsensitive, allowNullFields, allowOverride), overrides)
5058

5159
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Struct)>]
5260
type JsonFSharpConverterAttribute
@@ -69,7 +77,7 @@ type JsonFSharpConverterAttribute
6977
let fsOptions = JsonFSharpOptions(unionEncoding, unionTagName, unionFieldsName, Default.UnionTagNamingPolicy, unionTagCaseInsensitive, allowNullFields, false)
7078

7179
override _.CreateConverter(typeToConvert) =
72-
JsonFSharpConverter.CreateConverter(typeToConvert, options, fsOptions)
80+
JsonFSharpConverter.CreateConverter(typeToConvert, options, fsOptions, null)
7381

7482
interface IJsonFSharpConverterAttribute with
7583
member this.Options = fsOptions

src/FSharp.SystemTextJson/Helpers.fs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module internal System.Text.Json.Serialization.Helpers
22

33
open System
4+
open System.Collections.Generic
45
open System.Text.Json
56
open FSharp.Reflection
67

@@ -52,14 +53,23 @@ let isSkippableFieldType (fsOptions: JsonFSharpOptions) (ty: Type) =
5253
isNullableFieldType fsOptions ty
5354
|| isSkippableType ty
5455

55-
let overrideOptions (ty: Type) (defaultOptions: JsonFSharpOptions) =
56-
if defaultOptions.AllowOverride then
57-
match ty.GetCustomAttributes(typeof<IJsonFSharpConverterAttribute>, true) |> Array.tryHead with
58-
| Some (:? IJsonFSharpConverterAttribute as attr) ->
59-
if attr.Options.UnionEncoding.HasFlag(JsonUnionEncoding.Inherit) then
60-
attr.Options.WithUnionEncoding(defaultOptions.UnionEncoding)
61-
else
62-
attr.Options
63-
| _ -> defaultOptions
56+
let overrideOptions (ty: Type) (defaultOptions: JsonFSharpOptions) (overrides: IDictionary<Type, JsonFSharpOptions>) =
57+
let inheritUnionEncoding (options: JsonFSharpOptions) =
58+
if options.UnionEncoding.HasFlag(JsonUnionEncoding.Inherit) then
59+
options.WithUnionEncoding(defaultOptions.UnionEncoding)
60+
else
61+
options
62+
63+
let applyAttributeOverride() =
64+
if defaultOptions.AllowOverride then
65+
match ty.GetCustomAttributes(typeof<IJsonFSharpConverterAttribute>, true) |> Array.tryHead with
66+
| Some (:? IJsonFSharpConverterAttribute as attr) -> attr.Options |> inheritUnionEncoding
67+
| _ -> defaultOptions
68+
else defaultOptions
69+
70+
if isNull overrides then
71+
applyAttributeOverride()
6472
else
65-
defaultOptions
73+
match overrides.TryGetValue(ty) with
74+
| true, options -> options |> inheritUnionEncoding
75+
| false, _ -> applyAttributeOverride()

src/FSharp.SystemTextJson/Options.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ module internal Default =
104104

105105
type JsonFSharpOptions
106106
(
107-
[<Optional; DefaultParameterValue(Default.UnionEncoding)>]
107+
[<Optional; DefaultParameterValue(Default.UnionEncoding ||| JsonUnionEncoding.Inherit)>]
108108
unionEncoding: JsonUnionEncoding,
109109
[<Optional; DefaultParameterValue(Default.UnionTagName)>]
110110
unionTagName: JsonUnionTagName,

src/FSharp.SystemTextJson/Record.fs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,14 @@ type JsonRecordConverter(fsOptions: JsonFSharpOptions) =
161161
static member internal CanConvert(typeToConvert) =
162162
TypeCache.isRecord typeToConvert
163163

164-
static member internal CreateConverter(typeToConvert: Type, options: JsonSerializerOptions, fsOptions: JsonFSharpOptions) =
165-
let fsOptions = overrideOptions typeToConvert fsOptions
164+
static member internal CreateConverter
165+
(
166+
typeToConvert: Type,
167+
options: JsonSerializerOptions,
168+
fsOptions: JsonFSharpOptions,
169+
overrides: IDictionary<Type, JsonFSharpOptions>
170+
) =
171+
let fsOptions = overrideOptions typeToConvert fsOptions overrides
166172
typedefof<JsonRecordConverter<_>>
167173
.MakeGenericType([|typeToConvert|])
168174
.GetConstructor([|typeof<JsonSerializerOptions>; typeof<JsonFSharpOptions>|])
@@ -173,4 +179,4 @@ type JsonRecordConverter(fsOptions: JsonFSharpOptions) =
173179
JsonRecordConverter.CanConvert(typeToConvert)
174180

175181
override _.CreateConverter(typeToConvert, options) =
176-
JsonRecordConverter.CreateConverter(typeToConvert, options, fsOptions)
182+
JsonRecordConverter.CreateConverter(typeToConvert, options, fsOptions, null)

src/FSharp.SystemTextJson/Union.fs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ type private Case =
2727
MinExpectedFieldCount: int
2828
}
2929

30-
type JsonUnionConverter<'T>(options: JsonSerializerOptions, fsOptions: JsonFSharpOptions, cases: UnionCaseInfo[]) =
30+
type JsonUnionConverter<'T>
31+
(
32+
options: JsonSerializerOptions,
33+
fsOptions: JsonFSharpOptions,
34+
cases: UnionCaseInfo[],
35+
overrides: IDictionary<Type, JsonFSharpOptions>
36+
) =
3137
inherit JsonConverter<'T>()
3238

3339
let [<Literal>] UntaggedBit = enum<JsonUnionEncoding> 0x00_08
@@ -76,7 +82,7 @@ type JsonUnionConverter<'T>(options: JsonSerializerOptions, fsOptions: JsonFShar
7682
&& FSharpType.IsRecord(fields.[0].Type, true)
7783
&& fsOptions.UnionEncoding.HasFlag JsonUnionEncoding.UnwrapRecordCases
7884
then
79-
JsonRecordConverter.CreateConverter(fields.[0].Type, options, fsOptions)
85+
JsonRecordConverter.CreateConverter(fields.[0].Type, options, fsOptions, overrides)
8086
|> box
8187
:?> IRecordConverter
8288
|> ValueSome
@@ -496,12 +502,19 @@ type JsonUnionConverter(fsOptions: JsonFSharpOptions) =
496502
static let fsOptionsTy = typeof<JsonFSharpOptions>
497503
static let caseTy = typeof<UnionCaseInfo>
498504
static let casesTy = typeof<UnionCaseInfo[]>
505+
static let overridesTy = typeof<IDictionary<Type, JsonFSharpOptions>>
499506

500507
static member internal CanConvert(typeToConvert) =
501508
TypeCache.isUnion typeToConvert
502509

503-
static member internal CreateConverter(typeToConvert: Type, options: JsonSerializerOptions, fsOptions: JsonFSharpOptions) =
504-
let fsOptions = overrideOptions typeToConvert fsOptions
510+
static member internal CreateConverter
511+
(
512+
typeToConvert: Type,
513+
options: JsonSerializerOptions,
514+
fsOptions: JsonFSharpOptions,
515+
overrides: IDictionary<Type, JsonFSharpOptions>
516+
) =
517+
let fsOptions = overrideOptions typeToConvert fsOptions overrides
505518
if fsOptions.UnionEncoding.HasFlag JsonUnionEncoding.UnwrapOption
506519
&& typeToConvert.IsGenericType
507520
&& typeToConvert.GetGenericTypeDefinition() = optionTy then
@@ -542,12 +555,12 @@ type JsonUnionConverter(fsOptions: JsonFSharpOptions) =
542555
else
543556
jsonUnionConverterTy
544557
.MakeGenericType([|typeToConvert|])
545-
.GetConstructor([|optionsTy; fsOptionsTy; casesTy|])
546-
.Invoke([|options; fsOptions; cases|])
558+
.GetConstructor([|optionsTy; fsOptionsTy; casesTy; overridesTy|])
559+
.Invoke([|options; fsOptions; cases; overrides|])
547560
:?> JsonConverter
548561

549562
override _.CanConvert(typeToConvert) =
550563
JsonUnionConverter.CanConvert(typeToConvert)
551564

552565
override _.CreateConverter(typeToConvert, options) =
553-
JsonUnionConverter.CreateConverter(typeToConvert, options, fsOptions)
566+
JsonUnionConverter.CreateConverter(typeToConvert, options, fsOptions, null)

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,24 @@ module NonStruct =
618618
o.Converters.Add(JsonFSharpConverter(allowOverride = true))
619619
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override2.A(123, "abc"), o))
620620

621+
[<Fact>]
622+
let ``should apply explicit overrides if allowOverride = false`` () =
623+
let o = JsonSerializerOptions()
624+
o.Converters.Add(JsonFSharpConverter(overrides = dict [typeof<Override>, JsonFSharpOptions(JsonUnionEncoding.InternalTag)]))
625+
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override.A(123, "abc"), o))
626+
627+
[<Fact>]
628+
let ``should apply explicit overrides if allowOverride = true`` () =
629+
let o = JsonSerializerOptions()
630+
o.Converters.Add(JsonFSharpConverter(allowOverride = true, overrides = dict [typeof<Override>, JsonFSharpOptions(JsonUnionEncoding.InternalTag)]))
631+
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override.A(123, "abc"), o))
632+
633+
[<Fact>]
634+
let ``should apply explicit overrides inheriting JsonUnionEncoding`` () =
635+
let o = JsonSerializerOptions()
636+
o.Converters.Add(JsonFSharpConverter(JsonUnionEncoding.InternalTag ||| JsonUnionEncoding.NamedFields, overrides = dict [typeof<Override>, JsonFSharpOptions(unionTagName = "tag")]))
637+
Assert.Equal("""{"tag":"A","x":123,"y":"abc"}""", JsonSerializer.Serialize(Override.A(123, "abc"), o))
638+
621639

622640
module Struct =
623641

@@ -1200,3 +1218,21 @@ module Struct =
12001218
let o = JsonSerializerOptions()
12011219
o.Converters.Add(JsonFSharpConverter(allowOverride = true))
12021220
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override2.A(123, "abc"), o))
1221+
1222+
[<Fact>]
1223+
let ``should apply explicit overrides if allowOverride = false`` () =
1224+
let o = JsonSerializerOptions()
1225+
o.Converters.Add(JsonFSharpConverter(overrides = dict [typeof<Override>, JsonFSharpOptions(JsonUnionEncoding.InternalTag)]))
1226+
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override.A(123, "abc"), o))
1227+
1228+
[<Fact>]
1229+
let ``should apply explicit overrides if allowOverride = true`` () =
1230+
let o = JsonSerializerOptions()
1231+
o.Converters.Add(JsonFSharpConverter(allowOverride = true, overrides = dict [typeof<Override>, JsonFSharpOptions(JsonUnionEncoding.InternalTag)]))
1232+
Assert.Equal("""["A",123,"abc"]""", JsonSerializer.Serialize(Override.A(123, "abc"), o))
1233+
1234+
[<Fact>]
1235+
let ``should apply explicit overrides inheriting JsonUnionEncoding`` () =
1236+
let o = JsonSerializerOptions()
1237+
o.Converters.Add(JsonFSharpConverter(JsonUnionEncoding.InternalTag ||| JsonUnionEncoding.NamedFields, overrides = dict [typeof<Override>, JsonFSharpOptions(unionTagName = "tag")]))
1238+
Assert.Equal("""{"tag":"A","x":123,"y":"abc"}""", JsonSerializer.Serialize(Override.A(123, "abc"), o))

0 commit comments

Comments
 (0)