Skip to content

Commit d1f04bb

Browse files
authored
Split autoUnion; add Options/Settings.Default (#73)
1 parent cee6011 commit d1f04bb

21 files changed

Lines changed: 126 additions & 88 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ The `Unreleased` section name is replaced by the expected version of next releas
99
## [Unreleased]
1010

1111
### Added
12+
13+
- `SystemTextJson`: Add `Options.Default` to match [`JsonSerializerSettings.Default`](https://github.com/dotnet/runtime/pull/61434) [#73](https://github.com/jet/FsCodec/pull/73)
14+
1215
### Changed
16+
17+
- `SystemTextJson`: Replace `autoUnion=true` with individually controllable `autoTypeSafeEnumToJsonString` and `autoUnionToJsonObject` settings re [#71](https://github.com/jet/FsCodec/pull/71) [#73](https://github.com/jet/FsCodec/pull/73)
18+
1319
### Removed
1420
### Fixed
1521

README.md

Lines changed: 36 additions & 31 deletions
Large diffs are not rendered by default.

src/FsCodec.NewtonsoftJson/Codec.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ type Codec private () =
7070
/// a <c>meta</c> object that will be serialized with the same settings (if it's not <c>None</c>)
7171
/// and an Event Creation <c>timestamp</c>.</summary>
7272
down : 'Context option * 'Event -> 'Contract * 'Meta option * Guid * string * string * DateTimeOffset option,
73-
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c></summary>
73+
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Default</c></summary>
7474
[<Optional; DefaultParameterValue(null)>] ?settings,
7575
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
7676
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
@@ -116,7 +116,7 @@ type Codec private () =
116116
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
117117
/// <summary>Uses the 'Context passed to the Encode call and the 'Meta emitted by <c>down</c> to a) the final metadata b) the <c>correlationId</c> and c) the correlationId</summary>
118118
mapCausation : 'Context option * 'Meta option -> 'Meta option * Guid * string * string,
119-
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c></summary>
119+
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Default</c></summary>
120120
[<Optional; DefaultParameterValue(null)>] ?settings,
121121
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
122122
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
@@ -142,7 +142,7 @@ type Codec private () =
142142
/// a <c>meta</c> object that will be serialized with the same settings (if it's not <c>None</c>)
143143
/// and an Event Creation <c>timestamp</c>.</summary>
144144
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
145-
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c></summary>
145+
/// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Default</c></summary>
146146
[<Optional; DefaultParameterValue(null)>] ?settings,
147147
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
148148
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
@@ -155,7 +155,7 @@ type Codec private () =
155155
/// The Event Type Names are inferred based on either explicit <c>DataMember(Name=</c> Attributes, or (if unspecified) the Discriminated Union Case Name
156156
/// <c>'Union</c> must be tagged with <c>interface TypeShape.UnionContract.IUnionContract</c> to signify this scheme applies.</summary>
157157
static member Create<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>
158-
( /// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c></summary>
158+
( /// <summary>Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Default</c></summary>
159159
[<Optional; DefaultParameterValue(null)>] ?settings,
160160
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
161161
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)

src/FsCodec.NewtonsoftJson/Serdes.fs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace FsCodec.NewtonsoftJson
22

3+
open FsCodec.NewtonsoftJson
34
open Newtonsoft.Json
45
open System.Runtime.InteropServices
56

@@ -24,15 +25,15 @@ type Serdes(options : JsonSerializerSettings) =
2425
value : 'T,
2526
/// Use indentation when serializing JSON. Defaults to false.
2627
[<Optional; DefaultParameterValue false>] ?indent : bool) : string =
27-
let options = (if indent = Some true then Settings.Create(indent = true) else Settings.Create())
28+
let options = (if indent = Some true then Settings.Create(indent = true) else Settings.Default)
2829
JsonConvert.SerializeObject(value, options)
2930

3031
/// Serializes given value to a JSON string with custom options
3132
[<System.Obsolete "Please use non-static Serdes instead">]
3233
static member Serialize<'T>
3334
( /// Value to serialize.
3435
value : 'T,
35-
/// Settings to use (use other overload to use Settings.Create() profile)
36+
/// Settings to use (use other overload to use Settings.Default profile)
3637
settings : JsonSerializerSettings) : string =
3738
JsonConvert.SerializeObject(value, settings)
3839

@@ -41,7 +42,7 @@ type Serdes(options : JsonSerializerSettings) =
4142
static member Deserialize<'T>
4243
( /// Json string to deserialize.
4344
json : string,
44-
/// Settings to use (defaults to Settings.Create() profile)
45+
/// Settings to use (defaults to Settings.Default profile)
4546
[<Optional; DefaultParameterValue null>] ?settings : JsonSerializerSettings) : 'T =
46-
let settings = match settings with Some x -> x | None -> Settings.Create()
47+
let settings = match settings with Some x -> x | None -> Settings.Default
4748
JsonConvert.DeserializeObject<'T>(json, settings)

src/FsCodec.NewtonsoftJson/Settings.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ type Settings private () =
99

1010
static let defaultConverters : JsonConverter[] = [| OptionConverter() |]
1111

12+
static let def = lazy Settings.Create()
13+
14+
/// <summary>Analogous to <c>JsonSerializerOptions.Default</c> - allows for sharing/caching of the default profile as defined by <c>Settings.Create()</c></summary>
15+
static member Default : JsonSerializerSettings = def.Value
16+
1217
/// Creates a default set of serializer settings used by Json serialization. When used with no args, same as JsonSerializerSettings.CreateDefault()
1318
static member CreateDefault
1419
( [<Optional; ParamArray>] converters : JsonConverter[],

src/FsCodec.SystemTextJson/Codec.fs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ open System.Text.Json
2424
/// See <a href="https://github.com/eiriktsarpalis/TypeShape/blob/master/tests/TypeShape.Tests/UnionContractTests.fs"></a> for example usage.</summary>
2525
type Codec private () =
2626

27-
static let defaultOptions = lazy Options.Create()
28-
2927
/// <summary>Generate an <c>IEventCodec</c> using the supplied <c>System.Text.Json</c> <c>options</c>.<br/>
3028
/// Uses <c>up</c> and <c>down</c> functions to facilitate upconversion/downconversion
3129
/// and/or surfacing metadata to the Programming Model by including it in the emitted <c>'Event</c><br/>
@@ -40,13 +38,13 @@ type Codec private () =
4038
/// a <c>meta</c> object that will be serialized with the same options (if it's not <c>None</c>)
4139
/// and an Event Creation <c>timestamp</c><summary>.
4240
down : 'Context option * 'Event -> 'Contract * 'Meta option * Guid * string * string * DateTimeOffset option,
43-
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Create()</c></summary>
41+
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Default</c></summary>
4442
[<Optional; DefaultParameterValue(null)>] ?options,
4543
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
4644
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
4745
: FsCodec.IEventCodec<'Event, JsonElement, 'Context> =
4846

49-
let options = match options with Some x -> x | None -> defaultOptions.Value
47+
let options = match options with Some x -> x | None -> Options.Default
5048
let elementEncoder : TypeShape.UnionContract.IEncoder<_> = Core.JsonElementEncoder(options) :> _
5149
let dataCodec =
5250
TypeShape.UnionContract.UnionContractEncoder.Create<'Contract, JsonElement>(
@@ -84,7 +82,7 @@ type Codec private () =
8482
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
8583
/// <summary>Uses the 'Context passed to the Encode call and the 'Meta emitted by <c>down</c> to a) the final metadata b) the <c>correlationId</c> and c) the correlationId</summary>
8684
mapCausation : 'Context option * 'Meta option -> 'Meta option * Guid * string * string,
87-
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Create()</c></summary>
85+
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Default</c></summary>
8886
[<Optional; DefaultParameterValue(null)>] ?options,
8987
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
9088
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
@@ -110,7 +108,7 @@ type Codec private () =
110108
/// a <c>meta</c> object that will be serialized with the same options (if it's not <c>None</c>)
111109
/// and an Event Creation <c>timestamp</c>.</summary>
112110
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
113-
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Create()</c></summary>
111+
/// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Default</c></summary>
114112
[<Optional; DefaultParameterValue(null)>] ?options,
115113
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
116114
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
@@ -123,7 +121,7 @@ type Codec private () =
123121
/// The Event Type Names are inferred based on either explicit <c>DataMember(Name=</c> Attributes, or (if unspecified) the Discriminated Union Case Name
124122
/// <c>'Union</c> must be tagged with <c>interface TypeShape.UnionContract.IUnionContract</c> to signify this scheme applies.</summary>
125123
static member Create<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>
126-
( /// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Create()</c></summary>
124+
( /// <summary>Configuration to be used by the underlying <c>System.Text.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Options.Default</c></summary>
127125
[<Optional; DefaultParameterValue(null)>] ?options,
128126
/// <summary>Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them</summary>
129127
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)

src/FsCodec.SystemTextJson/Interop.fs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,8 @@ type InteropExtensions =
4242
else JsonSerializer.Deserialize(System.ReadOnlySpan.op_Implicit x)
4343
static member private MapTo(x: JsonElement) : byte[] =
4444
if x.ValueKind = JsonValueKind.Undefined then null
45-
else JsonSerializer.SerializeToUtf8Bytes(x, options = InteropExtensions.NoOverEscapingOptions)
46-
// Avoid introduction of HTML escaping for things like quotes etc (as standard Options.Create() profile does)
47-
static member private NoOverEscapingOptions =
48-
System.Text.Json.JsonSerializerOptions(Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping)
45+
// Avoid introduction of HTML escaping for things like quotes etc (Options.Default uses Options.Create(), which defaults to unsafeRelaxedJsonEscaping=true)
46+
else JsonSerializer.SerializeToUtf8Bytes(x, options = Options.Default)
4947

5048
[<Extension>]
5149
static member ToByteArrayCodec<'Event, 'Context>(native : FsCodec.IEventCodec<'Event, JsonElement, 'Context>)

src/FsCodec.SystemTextJson/Options.fs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ open System.Text.Json.Serialization
99

1010
type Options private () =
1111

12+
static let def = lazy Options.Create()
13+
14+
/// <summary>Analogous to <c>JsonSerializerOptions.Default</c> - allows for sharing/caching of the default profile as defined by <c>Options.Create()</c></summary>
15+
static member Default : JsonSerializerOptions = def.Value
16+
1217
/// Creates a default set of serializer options used by Json serialization. When used with no args, same as `JsonSerializerOptions()`
1318
static member CreateDefault
1419
( [<Optional; ParamArray>] converters : JsonConverter[],
@@ -49,16 +54,18 @@ type Options private () =
4954
[<Optional; DefaultParameterValue(null)>] ?ignoreNulls : bool,
5055
/// Drop escaping of HTML-sensitive characters. defaults to `true`.
5156
[<Optional; DefaultParameterValue(null)>] ?unsafeRelaxedJsonEscaping : bool,
52-
/// <summary>Apply convention-based Union conversion using <c>TypeSafeEnumConverter</c> if possible, or <c>UnionEncoder</c> for all Discriminated Unions.
53-
/// defaults to <c>false</c>.</summary>
54-
[<Optional; DefaultParameterValue(null)>] ?autoUnion : bool) =
57+
/// <summary>Apply <c>TypeSafeEnumConverter</c> if possible. Defaults to <c>false</c>.</summary>
58+
[<Optional; DefaultParameterValue(null)>] ?autoTypeSafeEnumToJsonString : bool,
59+
/// <summary>Apply <c>UnionConverter</c> for all Discriminated Unions, if <c>TypeSafeEnumConverter</c> not possible. Defaults to <c>false</c>.</summary>
60+
[<Optional; DefaultParameterValue(null)>] ?autoUnionToJsonObject : bool) =
5561

5662
Options.CreateDefault(
5763
converters =
58-
( if autoUnion = Some true then
59-
let converter : JsonConverter array = [| UnionOrTypeSafeEnumConverterFactory() |]
64+
( match autoTypeSafeEnumToJsonString = Some true, autoUnionToJsonObject = Some true with
65+
| tse, u when tse || u ->
66+
let converter : JsonConverter array = [| UnionOrTypeSafeEnumConverterFactory(typeSafeEnum = tse, union = u) |]
6067
if converters = null then converter else Array.append converters converter
61-
else converters),
68+
| _ -> converters),
6269
?ignoreNulls = ignoreNulls,
6370
?indent = indent,
6471
?camelCase = camelCase,

src/FsCodec.SystemTextJson/Serdes.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ type Serdes(options : JsonSerializerOptions) =
2424
value : 'T,
2525
/// Use indentation when serializing JSON. Defaults to false.
2626
[<Optional; DefaultParameterValue false>] ?indent : bool) : string =
27-
let options = (if indent = Some true then Options.Create(indent = true) else Options.Create())
27+
let options = (if indent = Some true then Options.Create(indent = true) else Options.Default)
2828
JsonSerializer.Serialize<'T>(value, options)
2929

3030
/// Serializes given value to a JSON string with custom options
3131
[<System.Obsolete "Please use non-static Serdes instead">]
3232
static member Serialize<'T>
3333
( /// Value to serialize.
3434
value : 'T,
35-
/// Options to use (use other overload to use Options.Create() profile)
35+
/// Options to use (use other overload to use Options.Default profile)
3636
options : JsonSerializerOptions) : string =
3737
JsonSerializer.Serialize<'T>(value, options)
3838

@@ -41,7 +41,7 @@ type Serdes(options : JsonSerializerOptions) =
4141
static member Deserialize<'T>
4242
( /// Json string to deserialize.
4343
json : string,
44-
/// Options to use (defaults to Options.Create() profile)
44+
/// Options to use (defaults to Options.Default profile)
4545
[<Optional; DefaultParameterValue null>] ?options : JsonSerializerOptions) : 'T =
46-
let settings = options |> Option.defaultWith Options.Create
46+
let settings = match options with Some o -> o | None -> Options.Default
4747
JsonSerializer.Deserialize<'T>(json, settings)

src/FsCodec.SystemTextJson/UnionOrTypeSafeEnumConverterFactory.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ open System.Text.Json.Serialization
66

77
type internal ConverterActivator = delegate of unit -> JsonConverter
88

9-
type UnionOrTypeSafeEnumConverterFactory() =
9+
type UnionOrTypeSafeEnumConverterFactory(typeSafeEnum, union) =
1010
inherit JsonConverterFactory()
1111

1212
let isIntrinsic (t : Type) =
@@ -17,6 +17,8 @@ type UnionOrTypeSafeEnumConverterFactory() =
1717
override _.CanConvert(t : Type) =
1818
Union.isUnion t
1919
&& not (isIntrinsic t)
20+
&& ((typeSafeEnum && union)
21+
|| typeSafeEnum = Union.hasOnlyNullaryCases t)
2022

2123
override _.CreateConverter(typ, _options) =
2224
let openConverterType = if Union.hasOnlyNullaryCases typ then typedefof<TypeSafeEnumConverter<_>> else typedefof<UnionConverter<_>>

0 commit comments

Comments
 (0)