-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
236 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
<WarningLevel>5</WarningLevel> | ||
<IsTestProject>false</IsTestProject> | ||
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference> | ||
<DisableImplicitSystemValueTupleReference>true</DisableImplicitSystemValueTupleReference> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="JsonElementHelpers.fs" /> | ||
<Compile Include="Utf8JsonReaderExtensions.fs" /> | ||
<Compile Include="JsonOptionConverter.fs" /> | ||
<Compile Include="JsonRecordConverter.fs" /> | ||
<Compile Include="Options.fs" /> | ||
<Compile Include="Serdes.fs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
<PackageReference Include="MinVer" Version="2.0.0" PrivateAssets="All" /> | ||
|
||
<PackageReference Include="FSharp.Core" Version="4.3.4" Condition=" '$(TargetFramework)' == 'netstandard2.1' " /> | ||
|
||
<PackageReference Include="System.Text.Json" Version="4.7.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="../FsCodec/FsCodec.fsproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,52 @@ | ||
namespace FsCodec.SystemTextJson.Serialization | ||
namespace FsCodec.SystemTextJson | ||
|
||
open FsCodec.SystemTextJson.Serialization | ||
open System | ||
open System.Runtime.InteropServices | ||
open System.Text.Json | ||
open System.Text.Json.Serialization | ||
|
||
[<AutoOpen>] | ||
module JsonSerializerOptionExtensions = | ||
type JsonSerializerOptions with | ||
static member Create() = | ||
let options = JsonSerializerOptions() | ||
options.Converters.Add(new JsonRecordConverter()) | ||
options | ||
type Options private () = | ||
|
||
module JsonSerializer = | ||
let defaultOptions = JsonSerializerOptions.Create() | ||
static let defaultConverters : JsonConverterFactory[] = [| JsonOptionConverter(); JsonRecordConverter() |] | ||
|
||
/// Creates a default set of serializer options used by Json serialization. When used with no args, same as `JsonSerializerOptions()` | ||
static member CreateDefault | ||
( [<Optional; ParamArray>] converters : JsonConverterFactory[], | ||
/// Use multi-line, indented formatting when serializing JSON; defaults to false. | ||
[<Optional; DefaultParameterValue(null)>] ?indent : bool, | ||
/// Render idiomatic camelCase for PascalCase items by using `PropertyNamingPolicy = CamelCase`. Defaults to false. | ||
[<Optional; DefaultParameterValue(null)>] ?camelCase : bool, | ||
/// Ignore null values in input data; defaults to false. | ||
[<Optional; DefaultParameterValue(null)>] ?ignoreNulls : bool) = | ||
|
||
let indent = defaultArg indent false | ||
let camelCase = defaultArg camelCase false | ||
let ignoreNulls = defaultArg ignoreNulls false | ||
let options = JsonSerializerOptions() | ||
if converters <> null then converters |> Array.iter options.Converters.Add | ||
if indent then options.WriteIndented <- true | ||
if camelCase then options.PropertyNamingPolicy <- JsonNamingPolicy.CamelCase; options.DictionaryKeyPolicy <- JsonNamingPolicy.CamelCase | ||
if ignoreNulls then options.IgnoreNullValues <- true | ||
options | ||
|
||
/// Opinionated helper that creates serializer settings that provide good defaults for F# | ||
/// - Always prepends `[JsonOptionConverter(); JsonRecordConverter()]` to any converters supplied | ||
/// - no camel case conversion - assumption is you'll use records with camelCased names | ||
/// Everything else is as per CreateDefault:- i.e. emit nulls instead of omitting fields, no indenting, no camelCase conversion | ||
static member Create | ||
( /// List of converters to apply. Implicit [JsonOptionConverter(); JsonRecordConverter()] will be prepended and/or be used as a default | ||
[<Optional; ParamArray>] converters : JsonConverterFactory[], | ||
/// Use multi-line, indented formatting when serializing JSON; defaults to false. | ||
[<Optional; DefaultParameterValue(null)>] ?indent : bool, | ||
/// Render idiomatic camelCase for PascalCase items by using `PropertyNamingPolicy = CamelCase`. | ||
/// Defaults to false on basis that you'll use record and tuple field names that are camelCase (but thus not `CLSCompliant`). | ||
[<Optional; DefaultParameterValue(null)>] ?camelCase : bool, | ||
/// Ignore null values in input data; defaults to `false`. | ||
[<Optional; DefaultParameterValue(null)>] ?ignoreNulls : bool) = | ||
|
||
Options.CreateDefault( | ||
converters = (match converters with null | [||] -> defaultConverters | xs -> Array.append defaultConverters xs), | ||
?ignoreNulls = ignoreNulls, | ||
?indent = indent, | ||
?camelCase = camelCase) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
namespace FsCodec.SystemTextJson | ||
|
||
open System.Runtime.InteropServices | ||
open System.Text.Json | ||
|
||
/// Serializes to/from strings using the Options arising from a call to <c>Options.Create()</c> | ||
type Serdes private () = | ||
|
||
static let defaultOptions = lazy Options.Create() | ||
static let indentOptions = lazy Options.Create(indent = true) | ||
|
||
/// Serializes given value to a JSON string. | ||
static member Serialize<'T> | ||
( /// Value to serialize. | ||
value : 'T, | ||
/// Use indentation when serializing JSON. Defaults to false. | ||
[<Optional; DefaultParameterValue null>] ?indent : bool) : string = | ||
let options = (if defaultArg indent false then indentOptions else defaultOptions).Value | ||
JsonSerializer.Serialize(value, options) | ||
|
||
/// Serializes given value to a JSON string with custom options | ||
static member Serialize<'T> | ||
( /// Value to serialize. | ||
value : 'T, | ||
/// Options to use (use other overload to use Options.Create() profile) | ||
options : JsonSerializerOptions) : string = | ||
JsonSerializer.Serialize(value, options) | ||
|
||
/// Deserializes value of given type from JSON string. | ||
static member Deserialize<'T> | ||
( /// Json string to deserialize. | ||
json : string, | ||
/// Options to use (defaults to Options.Create() profile) | ||
[<Optional; DefaultParameterValue null>] ?options : JsonSerializerOptions) : 'T = | ||
let settings = match options with None -> defaultOptions.Value | Some x -> x | ||
JsonSerializer.Deserialize<'T>(json, settings) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
tests/FsCodec.SystemTextJson.Tests/FsCodec.SystemTextJson.Tests.fsproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
<WarningLevel>5</WarningLevel> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | ||
<PackageReference Include="Unquote" Version="5.0.0" /> | ||
<PackageReference Include="xunit" Version="2.4.1" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="../../src/FsCodec.SystemTextJson/FsCodec.SystemTextJson.fsproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="SerdesTests.fs" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
module FsCodec.SystemTextJson.Tests.SerdesTests | ||
|
||
open FsCodec.SystemTextJson | ||
open Swensen.Unquote | ||
open Xunit | ||
|
||
type Record = { a : int } | ||
|
||
type RecordWithOption = { a : int; b : string option } | ||
|
||
(* Characterization tests for OOTB JSON.NET | ||
The aim here is to characterize the gaps that we'll shim; we only want to do that as long as it's actually earranted *) | ||
module StjCharacterization = | ||
let ootbOptions = Options.CreateDefault() | ||
|
||
let [<Fact>] ``OOTB STJ records`` () = | ||
let value = { a = 1 } | ||
let ser = Serdes.Serialize(value, ootbOptions) | ||
test <@ ser = """{"a":1}""" @> | ||
|
||
let res = try let v = Serdes.Deserialize(ser, ootbOptions) in Choice1Of2 v with e -> Choice2Of2 e.Message | ||
test <@ match res with | ||
| Choice1Of2 v -> v = value | ||
| Choice2Of2 m -> m.Contains "Deserialization of reference types without parameterless constructor is not supported. Type 'FsCodec.SystemTextJson.Tests.SerdesTests+Record'" @> | ||
|
||
let [<Fact>] ``OOTB STJ options`` () = | ||
let ootbOptionsWithRecordConverter = Options.CreateDefault(converters = [|Serialization.JsonRecordConverter()|]) | ||
let value = { a = 1; b = Some "str" } | ||
let ser = Serdes.Serialize(value, ootbOptions) | ||
test <@ ser = """{"a":1,"b":{"Value":"str"}}""" @> | ||
let correctSer = """{"a":1,"b":"str"}""" | ||
let res = try let v = Serdes.Deserialize(correctSer, ootbOptionsWithRecordConverter) in Choice1Of2 v with e -> Choice2Of2 e.Message | ||
test <@ match res with | ||
| Choice1Of2 v -> v = value | ||
| Choice2Of2 m -> m.Contains "The JSON value could not be converted to Microsoft.FSharp.Core.FSharpOption`1[System.String]" @> | ||
|
||
(* Serdes + default Options behavior, i.e. the stuff we do *) | ||
|
||
let [<Fact>] records () = | ||
let value = { a = 1 } | ||
let res = Serdes.Serialize value | ||
test <@ res = """{"a":1}""" @> | ||
let des = Serdes.Deserialize res | ||
test <@ value = des @> | ||
|
||
let [<Fact>] options () = | ||
let value = { a = 1; b = Some "str" } | ||
let ser = Serdes.Serialize value | ||
test <@ ser = """{"a":1,"b":"str"}""" @> | ||
let des = Serdes.Deserialize ser | ||
test <@ value = des @> |