Skip to content

Commit b81db91

Browse files
authored
Replace JsonRecordConverter with Dependency on 5.0.0-preview.3 (#49)
1 parent a7c80f4 commit b81db91

File tree

7 files changed

+69
-196
lines changed

7 files changed

+69
-196
lines changed

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ 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 >= 4.7.0`, `TypeShape >= 8`
24+
- [depends](https://www.fuget.org/packages/FsCodec.SystemTextJson) on `FsCodec`, `System.Text.Json >= 5.0.0-preview.3`, `TypeShape >= 8`
2525

2626
Deltas in behavior/functionality vs `FsCodec.NewtonsoftJson`:
2727

@@ -70,13 +70,12 @@ While this may not seem like a sufficiently large set of converters for a large
7070

7171
### ... but don't forget `FSharp.SystemTextJson`
7272

73-
NOTE `System.Text.Json`, esp in the .NET Core 3 era is very spartan, especially wrt F# support: it doesnt support unions, options, lists or records out of the box. Its hoped that over time the base support in there will improve and the shimming FsCodec does (e.g. `JsonRecordConverter`) can be reduced. It's worth calling out explicitly that there are no plans to extend the representations `FsCodec.SystemTextJson` can handle in any significant way over time - if you have specific exotic corner cases and determine you need something more specifically tailored, the Converters abstraction affords you ability to mix and match from the [`FSharp.SystemTextJson`](https://github.com/Tarmil/FSharp.SystemTextJson) library - it provides a much broader and complete (and well tested) set of converters with a broader remit than what FsCodec is trying to maintain as its sweet spot.
73+
`System.Text.Json` v `4.x` did not even support F# records that are not marked `[<CLIMutable>]` out-of-the-box (it was similarly spartan wrt C# types, requiring a default constructor on `class`es). The `>= 5.0` that `FsCodec.System.Text.Json` requires does support records, but it doesnt support Discriminated Unions, `option`s, `list`s, `Set` or `Map` out of the box. It's worth calling out explicitly that there are no plans to extend the representations `FsCodec.SystemTextJson` can handle in any significant way over time ([the advice for `FsCodec.NewtonsoftJson` has always been to avoid stuff outside of records, `option`s and `array`s](#recommendations)) - if you have specific exotic corner cases and determine you need something more specifically tailored, the Converters abstraction affords you ability to mix and match from the [`FSharp.SystemTextJson`](https://github.com/Tarmil/FSharp.SystemTextJson) library - it provides a much broader and complete (and well tested) set of converters with a broader remit than what FsCodec is trying to maintain as its sweet spot.
7474

7575
### Core converters
7676

7777
The respective concrete Codec packages include relevant `Converter`/`JsonConverter` in order to facilitate interoperable and versionable renderings:
7878
- `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)
79-
- [`JsonRecordConverter`](https://github.com/jet/FsCodec/blob/stj/src/FsCodec.SystemTextJson/JsonRecordConverter.fs#L18) represents F# records as a JSON Object; included in the standard `Options.Create` profile [as [`System.Text.Json` does not support F# records out of the box](https://github.com/dotnet/runtime/issues/29812)]. :pray: [@ylibrach](https://github.com/ylibrach)
8079
- [`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)
8180
- [`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)
8281

@@ -107,7 +106,7 @@ The respective concrete Codec packages include relevant `Converter`/`JsonConvert
107106
[`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:
108107
- `CreateDefault`: equivalent to generating a `new JsonSerializerSettings()` without any overrides of any kind
109108
- `Create`: as `CreateDefault` with the following difference:
110-
- adds a `JsonOptionConverter` and a `JsonRecordConverter`; included in default `Settings` (see _Converters_, below)
109+
- adds a `JsonOptionConverter`; included in default `Settings` (see _Converters_, below)
111110
- Inhibits the HTML-safe escaping that `System.Text.Json` provides as a default by overriding `Encoder` with `System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping`
112111

113112
## `Serdes`
@@ -159,7 +158,7 @@ module Contract =
159158
<a name="recommendations"></a>
160159
### Recommended round-trippable constructs
161160

162-
`Newtonsoft.Json`, thanks to its broad usage thoughout .NET systems has well known (with some idiosyncratic quirks) behaviors for most common types one might use for C# DTOs.
161+
`Newtonsoft.Json`, thanks to its broad usage throughout .NET systems has well known (with some idiosyncratic quirks) behaviors for most common types one might use for C# DTOs.
163162

164163
Normal primitive F#/.NET such as `bool`, `byte`, `int16`, `int`, `int64`, `float32` (`Single`), `float` (`Double`), `decimal` work as expected.
165164

@@ -176,7 +175,7 @@ The recommendations here apply particularly to Event Contracts - the data in you
176175
| `string` | As per C#; need to handle `null` | One can use a `string option` to map `null` and `Some null` to `None` | `"Abc"` | `"Abc"` |
177176
| 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` |
178177
| [`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"` |
179-
| records | Just work | For `System.Text.Json`, there's a `JsonRecordConverter` in the standard `Options`, as records are not supported out of the box | `{\| a = 1; b = Some "x" \|}` | `"{"a":1,"b":"x"}"` |
178+
| records | Just work | For `System.Text.Json` v `4.x`, usage of `[<CLIMutable>]` or a custom `JsonRecordConverter` was once required | `{\| a = 1; b = Some "x" \|}` | `"{"a":1,"b":"x"}"` |
180179
| Nullary unions (Enum-like DU's without bodies) | Tag `type` with `TypeSafeEnumConverter` | Works well - guarantees a valid mapping, as opposed to using a `System.Enum` and `StringEnumConverter`, which can map invalid values and/or silently map to `0` etc | `State.NotFound` | `"NotFound"` |
181180
| Discriminated Unions (where one or more cases has a body) | Tag `type` with `UnionConverter` | This format can be readily consumed in Java, JavaScript and Swift. Nonetheless, exhaust all other avenues before considering encoding a union in JSON. The `"case"` label id can be overridden. | `Decision.Accepted { result = "54" }` | `{"case": "Accepted","result":"54"}` |
182181

@@ -187,10 +186,10 @@ The mechanisms in the previous section have proven themselves sufficient for div
187186
| Type kind | TL;DR | Example input | Example output | Notes |
188187
| :--- | :--- | :--- | :--- | :--- |
189188
| `'t list` | __Don't use__; use `'t[]` | `[ 1; 2; 3]` | `[1,2,3]` | While the happy path works, `null` or missing field maps to a `null` object rather than `[]` [which is completely wrong from an F# perspective] |
190-
| `DateTime` | __Don't use__; use `DateTimeOffset` | | | Roundtripping can be messy, wrong or lossy; `DateTimeOffset` covers same use cases |
189+
| `DateTime` | __Don't use__; use `DateTimeOffset` | | | Round-tripping can be messy, wrong or lossy; `DateTimeOffset` covers same use cases |
191190
| `Guid` or [`FSharp.UMX`](https://github.com/fsprojects/FSharp.UMX) tagged `Guid` | __don't use__; wrap as a reference `type` and use a `JsonIsomorphism`, or represent as a tagged `string` | `Guid.NewGuid()` | `"ba7024c7-6795-413f-9f11-d3b7b1a1fe7a"` | If you wrap the value in a type, you can have that roundtrip with a specific format via a Converter implemented as a `JsonIsomorphism`. Alternately, represent in your contract as a [`FSharp.UMX`](https://github.com/fsprojects/FSharp.UMX) tagged-string. |
192-
| maps/`Dictionary` etc. | avoid; prefer arrays | | | As per C#; not always the best option for many reasons, both on the producer and consumer side. Json.NET has support for various maps with various idiosyncracies typically best covered by Stack Overflow, but often a list of records is clearer |
193-
| tuples | __Don't use__; use records | `(1,2)` | `{"Item1":1,"Item2":2}` | While converters are out there, using tuples in contracts ofany kind is simply Not A Good Idea |
191+
| maps/`Dictionary` etc. | avoid; prefer arrays | | | As per C#; not always the best option for many reasons, both on the producer and consumer side. Json.NET has support for various maps with various idiosyncracies typically best covered by Stack Overflow, but often a list of records is clearer<br/>For `System.Text.Json`, use an `IDictionary<'K, 'V>` or `Dictionary<'K, 'V>` |
192+
| tuples | __Don't use__; use records | `(1,2)` | `{"Item1":1,"Item2":2}` | While converters are out there, using tuples in contracts of any kind is simply Not A Good Idea |
194193

195194
<a name="JsonIsomorphism"></a>
196195
## Custom converters using `JsonIsomorphism`

src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<ItemGroup>
1212
<Compile Include="JsonSerializerElementExtensions.fs" />
1313
<Compile Include="JsonOptionConverter.fs" />
14-
<Compile Include="JsonRecordConverter.fs" />
1514
<Compile Include="Pickler.fs" />
1615
<Compile Include="UnionConverter.fs" />
1716
<Compile Include="TypeSafeEnumConverter.fs" />
@@ -27,7 +26,7 @@
2726

2827
<PackageReference Include="FSharp.Core" Version="4.3.4" />
2928

30-
<PackageReference Include="System.Text.Json" Version="4.7.0" />
29+
<PackageReference Include="System.Text.Json" Version="5.0.0-preview.3.20214.6" />
3130
<PackageReference Include="TypeShape" Version="8.0.0" />
3231
</ItemGroup>
3332

src/FsCodec.SystemTextJson/JsonRecordConverter.fs

Lines changed: 0 additions & 159 deletions
This file was deleted.

src/FsCodec.SystemTextJson/Options.fs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ open System.Text.Json.Serialization
77

88
type Options private () =
99

10-
static let defaultConverters : JsonConverter[] =
11-
[| Converters.JsonOptionConverter()
12-
Converters.JsonRecordConverter() |]
10+
static let defaultConverters : JsonConverter[] = [| Converters.JsonOptionConverter() |]
1311

1412
/// Creates a default set of serializer options used by Json serialization. When used with no args, same as `JsonSerializerOptions()`
1513
static member CreateDefault
@@ -36,12 +34,12 @@ type Options private () =
3634
options
3735

3836
/// Opinionated helper that creates serializer settings that represent good defaults for F# <br/>
39-
/// - Always prepends `[JsonOptionConverter(); JsonRecordConverter()]` to any converters supplied <br/>
37+
/// - Always prepends `[JsonOptionConverter()]` to any converters supplied <br/>
4038
/// - no camel case conversion - assumption is you'll use records with camelCased names <br/>
4139
/// - renders values with `UnsafeRelaxedJsonEscaping` - i.e. minimal escaping as per `NewtonsoftJson`<br/>
4240
/// Everything else is as per CreateDefault:- i.e. emit nulls instead of omitting fields, no indenting, no camelCase conversion
4341
static member Create
44-
( /// List of converters to apply. Implicit [JsonOptionConverter(); JsonRecordConverter()] will be prepended and/or be used as a default
42+
( /// List of converters to apply. Implicit [JsonOptionConverter()] will be prepended and/or be used as a default
4543
[<Optional; ParamArray>] converters : JsonConverter[],
4644
/// Use multi-line, indented formatting when serializing JSON; defaults to false.
4745
[<Optional; DefaultParameterValue(null)>] ?indent : bool,

0 commit comments

Comments
 (0)