Skip to content

Commit 58d4732

Browse files
committed
Target System.Text.Json 6.0.1
1 parent 56e6ad1 commit 58d4732

File tree

12 files changed

+35
-103
lines changed

12 files changed

+35
-103
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ The `Unreleased` section name is replaced by the expected version of next releas
1010

1111
### Added
1212
### Changed
13+
14+
- `SystemTextJson`: Target `System.Text.Json` v `6.0.1`, `TypeShape` v `10.0.0` [#59](https://github.com/jet/FsCodec/pull/59)
15+
1316
### Removed
1417
### Fixed
1518

README.md

+7-9
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ The components within this repository are delivered as multi-targeted Nuget pack
2121
- Provides relevant Converters for common non-primitive types prevalent in F#
2222
- [depends](https://www.fuget.org/packages/FsCodec.NewtonsoftJson) on `FsCodec`, `Newtonsoft.Json >= 11.0.2`, `TypeShape >= 8`, `Microsoft.IO.RecyclableMemoryStream >= 1.2.2`, `System.Buffers >= 4.5`
2323
- [![System.Text.Json Codec NuGet](https://img.shields.io/nuget/v/FsCodec.SystemTextJson.svg)](https://www.nuget.org/packages/FsCodec.SystemTextJson/) `FsCodec.SystemTextJson`: See [#38](https://github.com/jet/FsCodec/pulls/38): drop in replacement that allows one to retarget from `Newtonsoft.Json` to the .NET Core >= v 3.0 default serializer: `System.Text.Json`, solely by changing the referenced namespace.
24-
- [depends](https://www.fuget.org/packages/FsCodec.SystemTextJson) on `FsCodec`, `System.Text.Json >= 5.0.0`, `TypeShape >= 8`
24+
- [depends](https://www.fuget.org/packages/FsCodec.SystemTextJson) on `FsCodec`, `System.Text.Json >= 6.0.1`, `TypeShape >= 10`
2525

2626
Deltas in behavior/functionality vs `FsCodec.NewtonsoftJson`:
27-
28-
1. [`UnionConverter` is WIP](https://github.com/jet/FsCodec/pull/43); model-binding related functionality that `System.Text.Json` does not provide equivalents will not be carried forward (e.g., `MissingMemberHandling`)
27+
28+
1. [`UnionConverter` is WIP](https://github.com/jet/FsCodec/pull/43)
2929

3030
# Features: `FsCodec`
3131

@@ -75,7 +75,6 @@ While this may not seem like a sufficiently large set of converters for a large
7575
### Core converters
7676

7777
The respective concrete Codec packages include relevant `Converter`/`JsonConverter` in order to facilitate interoperable and versionable renderings:
78-
- `JsonOptionConverter` / [`OptionConverter`](https://github.com/jet/FsCodec/blob/master/src/FsCodec.NewtonsoftJson/OptionConverter.fs#L7) represents F#'s `Option<'t>` as a value or `null`; included in the standard `Settings.Create`/`Options.Create` profile. `System.Text.Json` reimplementation :pray: [@ylibrach](https://github.com/ylibrach)
7978
- [`TypeSafeEnumConverter`](https://github.com/jet/FsCodec/blob/master/src/FsCodec.NewtonsoftJson/TypeSafeEnumConverter.fs#L33) represents discriminated union (whose cases are all nullary), as a `string` in a trustworthy manner (`Newtonsoft.Json.Converters.StringEnumConverter` permits values outside the declared values) :pray: [@amjjd](https://github.com/amjjd)
8079
- [`UnionConverter`](https://github.com/jet/FsCodec/blob/master/src/FsCodec.NewtonsoftJson/UnionConverter.fs#L71) represents F# discriminated unions as a single JSON `object` with both the tag value and the body content as named fields directly within :pray: [@amjdd](https://github.com/amjjd); `System.Text.Json` reimplementation :pray: [@NickDarvey](https://github.com/NickDarvey)
8180

@@ -90,6 +89,7 @@ The respective concrete Codec packages include relevant `Converter`/`JsonConvert
9089

9190
### `FsCodec.NewtonsoftJson`-specific low level converters
9291

92+
- [`OptionConverter`](https://github.com/jet/FsCodec/blob/master/src/FsCodec.NewtonsoftJson/OptionConverter.fs#L7) represents F#'s `Option<'t>` as a value or `null`; included in the standard `Settings.Create` profile.
9393
- [`VerbatimUtf8JsonConverter`](https://github.com/jet/FsCodec/blob/master/src/FsCodec.NewtonsoftJson/VerbatimUtf8JsonConverter.fs#L7) captures/renders known valid UTF8 JSON data into a `byte[]` without decomposing it into an object model (not typically relevant for application level code, used in `Equinox.Cosmos` versions prior to `3.0`).
9494

9595
## `FsCodec.NewtonsoftJson.Settings`
@@ -106,7 +106,6 @@ The respective concrete Codec packages include relevant `Converter`/`JsonConvert
106106
[`FsCodec.SystemTextJson.Options`](https://github.com/jet/FsCodec/blob/stj/src/FsCodec.SystemTextJson/Options.fs#L8) provides a clean syntax for building a `System.Text.Json.Serialization.JsonSerializerOptions` as per `FsCodec.NewtonsoftJson.Settings`, above. Methods:
107107
- `CreateDefault`: equivalent to generating a `new JsonSerializerSettings()` without any overrides of any kind
108108
- `Create`: as `CreateDefault` with the following difference:
109-
- adds a `JsonOptionConverter`; included in default `Settings` (see _Converters_, below)
110109
- Inhibits the HTML-safe escaping that `System.Text.Json` provides as a default by overriding `Encoder` with `System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping`
111110

112111
## `Serdes`
@@ -126,7 +125,7 @@ If you follow the policies covered in the rest of the documentation here, your D
126125
2. Types that require a global converter to be registered. _While it may seem that the second set is open-ended and potentially vast, experience teaches that you want to keep it minimal._. This boils down to:
127126
- records arrays and all other good choices for types Just Work already
128127
- `Nullable<MyType>`: Handled out of the box by both NSJ and STJ - requires no converters, provides excellent interop with other CLR languages. Would recommend.
129-
- `MyType option`: Covered by the global `OptionConverter`/`JsonOptionConverter` (see below for a clean way to add them to the default MVC view rendering configuration). Note that while this works well with ASP.NET Core, it may be problematic if you share contracts (yes, not saying you should) or rely on things like Swashbuckle which will need to be aware of the types when they reflect over them.
128+
- `MyType option`: Covered by the global `OptionConverter` for Newtonsoft, handled intrinsically by `System.Text.Json` versions `>= 6` (see below for a clean way to add them to the default MVC view rendering configuration). Note that while this works well with ASP.NET Core, it may be problematic if you share contracts (yes, not saying you should) or rely on things like Swashbuckle which will need to be aware of the types when they reflect over them.
130129

131130
**The bottom line is that using exotic types in DTOs is something to think very hard about before descending into. The next sections are thus only relevant if you decide to add that extra complexity to your system...**
132131

@@ -153,7 +152,7 @@ The equivalent for the native `System.Text.Json` looks like this:
153152
|> Seq.iter options.JsonSerializerOptions.Converters.Add
154153
) |> ignore
155154

156-
_As of `System.Text.Json` v5, the only converter used under the hood is `FsCodec.SystemTextJson.JsonOptionConverter`. [In v6, the `OptionConverter` goes](https://github.com/dotnet/runtime/pull/55108)._
155+
_As of `System.Text.Json` v6, thanks [to the great work of the .NET team](https://github.com/dotnet/runtime/pull/55108), the above is presently a no-op._
157156

158157
# Examples: `FsCodec.(Newtonsoft|SystemText)Json`
159158

@@ -187,7 +186,6 @@ While it's hard to justify the wrapping in the previous case, this illustrates h
187186
module Contract =
188187
type Item = { value : string option; other : TypeThatRequiresMyCustomConverter }
189188
/// Settings to be used within this contract
190-
// note OptionConverter is also included by default
191189
let settings = FsCodec.NewtonsoftJson.Settings.Create(converters = [| MyCustomConverter() |])
192190
let serialize (x : Item) = FsCodec.NewtonsoftJson.Serdes.Serialize(x,settings)
193191
let deserialize (json : string) : Item = FsCodec.NewtonsoftJson.Serdes.Deserialize(json,settings)
@@ -211,7 +209,7 @@ The recommendations here apply particularly to Event Contracts - the data in you
211209
| `'t[]` | As per C# | Don't forget to handle `null` | `[ 1; 2; 3]` | `[1,2,3]` |
212210
| `DateTimeOffset` | Roundtrips cleanly | The default `Settings.Create` requests `RoundtripKind` | `DateTimeOffset.Now` | `"2019-09-04T20:30:37.272403+01:00"` |
213211
| `Nullable<'t>` | As per C#; `Nullable()` -> `null`, `Nullable x` -> `x` | OOTB Json.NET and STJ roundtrip cleanly. Works with `Settings.CreateDefault()`. Worth considering if your contract does not involve many `option` types | `Nullable 14` | `14` |
214-
| `'t option` | `Some null`,`None` -> `null`, `Some x` -> `x` _with the converter `Settings.Create()` adds_ | OOTB Json.NET and STJ do not roundtrip `option` types cleanly; `Settings/Options/Codec.Create` wire in an `OptionConverter` by default<br/> NOTE `Some null` will produce `null`, but deserialize as `None` - i.e., it's not round-trippable | `Some 14` | `14` |
212+
| `'t option` | `Some null`,`None` -> `null`, `Some x` -> `x` _with the converter `Settings.Create()` adds_ | OOTB Json.NET and STJ do not roundtrip `option` types cleanly; `Settings/Options/Codec.Create` wire in an `OptionConverter` by default in `FsCodec.NewtonsoftJson`<br/> NOTE `Some null` will produce `null`, but deserialize as `None` - i.e., it's not round-trippable | `Some 14` | `14` |
215213
| `string` | As per C#; need to handle `null` | One can use a `string option` to map `null` and `Some null` to `None` | `"Abc"` | `"Abc"` |
216214
| types with unit of measure | Works well (doesnt encode the unit) | Unit of measure tags are only known to the compiler; Json.NET does not process the tags and treats it as the underlying primitive type | `54<g>` | `54` |
217215
| [`FSharp.UMX`](https://github.com/fsprojects/FSharp.UMX) tagged `string`, `DateTimeOffset` | Works well | [`FSharp.UMX`](https://github.com/fsprojects/FSharp.UMX) enables one to type-tag `string` and `DateTimeOffset` values using the units of measure compiler feature, which Json.NET will render as if they were unadorned | `SkuId.parse "54-321"` | `"000-054-321"` |

src/FsCodec.NewtonsoftJson/UnionConverter.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module private Union =
2020
let isUnion = memoize (fun t -> FSharpType.IsUnion(t, true))
2121
let getUnionCases = memoize (fun t -> FSharpType.GetUnionCases(t, true))
2222

23-
let createUnion t =
23+
let private createUnion t =
2424
let cases = getUnionCases t
2525
{
2626
cases = cases

src/FsCodec.SystemTextJson/Codec.fs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ type JsonElementEncoder(options : JsonSerializerOptions) =
1010
member _.Encode(value : 'T) =
1111
JsonSerializer.SerializeToElement(value, options)
1212

13-
member _.Decode(json : JsonElement) =
14-
JsonSerializer.DeserializeElement(json, options)
13+
member _.Decode<'T>(json : JsonElement) =
14+
JsonSerializer.Deserialize<'T>(json, options)
1515

1616
namespace FsCodec.SystemTextJson
1717

src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<Compile Include="JsonSerializerElementExtensions.fs" />
12-
<Compile Include="JsonOptionConverter.fs" />
1311
<Compile Include="Pickler.fs" />
1412
<Compile Include="UnionConverter.fs" />
1513
<Compile Include="TypeSafeEnumConverter.fs" />
@@ -23,10 +21,11 @@
2321
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
2422
<PackageReference Include="MinVer" Version="2.5.0" PrivateAssets="All" />
2523

26-
<PackageReference Include="FSharp.Core" Version="4.3.4" />
24+
<!-- NOTE: Not 4.3.4 as typical, as TypeShape v10 demands higher -->
25+
<PackageReference Include="FSharp.Core" Version="4.5.4" />
2726

28-
<PackageReference Include="System.Text.Json" Version="5.0.1" />
29-
<PackageReference Include="TypeShape" Version="8.0.0" />
27+
<PackageReference Include="System.Text.Json" Version="6.0.1" />
28+
<PackageReference Include="TypeShape" Version="10.0.0" />
3029
</ItemGroup>
3130

3231
<ItemGroup>

src/FsCodec.SystemTextJson/Interop.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ 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, InteropExtensions.NoOverEscapingOptions)
45+
else JsonSerializer.SerializeToUtf8Bytes(x, options = InteropExtensions.NoOverEscapingOptions)
4646
// Avoid introduction of HTML escaping for things like quotes etc (as standard Options.Create() profile does)
4747
static member private NoOverEscapingOptions =
4848
System.Text.Json.JsonSerializerOptions(Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping)

src/FsCodec.SystemTextJson/JsonOptionConverter.fs

-36
This file was deleted.

src/FsCodec.SystemTextJson/JsonSerializerElementExtensions.fs

-27
This file was deleted.

src/FsCodec.SystemTextJson/Options.fs

+5-6
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ open System.Runtime.InteropServices
55
open System.Text.Json
66
open System.Text.Json.Serialization
77

8-
type Options private () =
8+
#nowarn "44" // see IgnoreNullValues below
99

10-
static let defaultConverters : JsonConverter[] = [| JsonOptionConverter() |]
10+
type Options private () =
1111

1212
/// Creates a default set of serializer options used by Json serialization. When used with no args, same as `JsonSerializerOptions()`
1313
static member CreateDefault
@@ -29,17 +29,16 @@ type Options private () =
2929
if converters <> null then converters |> Array.iter options.Converters.Add
3030
if indent then options.WriteIndented <- true
3131
if camelCase then options.PropertyNamingPolicy <- JsonNamingPolicy.CamelCase; options.DictionaryKeyPolicy <- JsonNamingPolicy.CamelCase
32-
if ignoreNulls then options.IgnoreNullValues <- true
32+
if ignoreNulls then options.IgnoreNullValues <- true // options.DefaultIgnoreCondition <- JsonIgnoreCondition.Always is outlawed so nowarn required
3333
if unsafeRelaxedJsonEscaping then options.Encoder <- System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
3434
options
3535

3636
/// Opinionated helper that creates serializer settings that represent good defaults for F# <br/>
37-
/// - Always prepends `[JsonOptionConverter()]` to any converters supplied <br/>
3837
/// - no camel case conversion - assumption is you'll use records with camelCased names <br/>
3938
/// - renders values with `UnsafeRelaxedJsonEscaping` - i.e. minimal escaping as per `NewtonsoftJson`<br/>
4039
/// Everything else is as per CreateDefault:- i.e. emit nulls instead of omitting fields, no indenting, no camelCase conversion
4140
static member Create
42-
( /// List of converters to apply. Implicit [JsonOptionConverter()] will be prepended and/or be used as a default
41+
( /// List of converters to apply. Implicit converters may be prepended and/or be used as a default
4342
[<Optional; ParamArray>] converters : JsonConverter[],
4443
/// Use multi-line, indented formatting when serializing JSON; defaults to false.
4544
[<Optional; DefaultParameterValue(null)>] ?indent : bool,
@@ -52,7 +51,7 @@ type Options private () =
5251
[<Optional; DefaultParameterValue(null)>] ?unsafeRelaxedJsonEscaping : bool) =
5352

5453
Options.CreateDefault(
55-
converters = (match converters with null | [||] -> defaultConverters | xs -> Array.append defaultConverters xs),
54+
converters = converters,
5655
?ignoreNulls = ignoreNulls,
5756
?indent = indent,
5857
?camelCase = camelCase,

tests/FsCodec.SystemTextJson.Tests/Examples.fsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ open System
2222
module Contract =
2323

2424
type Item = { value : string option }
25-
// implies default options from Options.Create(), which includes OptionConverter
25+
// implies default options from Options.Create()
2626
let serialize (x : Item) : string = FsCodec.SystemTextJson.Serdes.Serialize x
27-
// implies default options from Options.Create(), which includes OptionConverter
27+
// implies default options from Options.Create()
2828
let deserialize (json : string) = FsCodec.SystemTextJson.Serdes.Deserialize json
2929

3030
module Contract2 =
3131

3232
type TypeThatRequiresMyCustomConverter = { mess : int }
3333
type MyCustomConverter() = inherit JsonPickler<string>() override _.Read(_,_) = "" override _.Write(_,_,_) = ()
3434
type Item = { value : string option; other : TypeThatRequiresMyCustomConverter }
35-
/// Options to be used within this contract; note JsonOptionConverter is also included by default
35+
/// Options to be used within this contract
3636
let options = FsCodec.SystemTextJson.Options.Create(converters = [| MyCustomConverter() |])
3737
let serialize (x : Item) = FsCodec.SystemTextJson.Serdes.Serialize(x, options)
3838
let deserialize (json : string) : Item = FsCodec.SystemTextJson.Serdes.Deserialize(json, options)

0 commit comments

Comments
 (0)