Skip to content

Commit f537104

Browse files
authored
Merge pull request #201 from Tarmil/tuple-specialize
Specialize tuple converter up to size 4
2 parents b0483ad + 1631bc2 commit f537104

File tree

2 files changed

+194
-26
lines changed

2 files changed

+194
-26
lines changed

benchmarks/FSharp.SystemTextJson.Benchmarks/Program.fs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,30 @@ type ReflectionComparison() =
9494
for i in 0 .. this.Iterations do
9595
TypeCache.isRecord typeof<TestRecord> |> ignore
9696

97+
type TupleComparison() =
98+
99+
let fsharpOptions = JsonFSharpOptions.Default()
100+
let specializedOptions = fsharpOptions.ToJsonSerializerOptions()
101+
let genericOptions =
102+
let o = JsonSerializerOptions()
103+
o.Converters.Add(JsonTupleConverter(fsharpOptions, true))
104+
o
105+
106+
[<Benchmark>]
107+
member this.StructGeneric() =
108+
System.Text.Json.JsonSerializer.Deserialize<struct (int * bool)>("[1,true]", genericOptions)
109+
110+
[<Benchmark>]
111+
member this.StructSpecialized() =
112+
System.Text.Json.JsonSerializer.Deserialize<struct (int * bool)>("[1,true]", specializedOptions)
113+
114+
[<Benchmark>]
115+
member this.RefGeneric() =
116+
System.Text.Json.JsonSerializer.Deserialize<int * bool>("[1,true]", genericOptions)
117+
118+
[<Benchmark>]
119+
member this.RefSpecialized() =
120+
System.Text.Json.JsonSerializer.Deserialize<int * bool>("[1,true]", specializedOptions)
97121

98122
let config =
99123
ManualConfig
@@ -106,7 +130,8 @@ let defaultSwitch () =
106130
BenchmarkSwitcher(
107131
[| typeof<Records>
108132
typeof<Classes>
109-
typeof<ReflectionComparison> |]
133+
typeof<ReflectionComparison>
134+
typeof<TupleComparison> |]
110135
)
111136

112137

src/FSharp.SystemTextJson/Tuple.fs

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

33
open System
4+
open System.Runtime.InteropServices
45
open System.Text.Json
56
open System.Text.Json.Serialization.Helpers
67
open FSharp.Reflection
78

8-
type internal TupleProperty = { Type: Type; NeedsNullChecking: bool }
9+
type internal TupleProperty<'T> =
10+
{ Type: Type
11+
NeedsNullChecking: bool }
912

10-
type JsonTupleConverter<'T> internal (fsOptions) =
13+
static member MakeUntyped fsOptions t =
14+
let needsNullChecking =
15+
let tIsNullable = isNullableFieldType fsOptions t
16+
not tIsNullable && not t.IsValueType
17+
{ Type = t; NeedsNullChecking = needsNullChecking }
18+
19+
static member MakeTyped fsOptions =
20+
TupleProperty<'T>.MakeUntyped fsOptions typeof<'T>
21+
22+
member this.CheckNull(x: 'T) =
23+
if this.NeedsNullChecking && isNull (box x) then
24+
failf "Unexpected null inside tuple-array. Expected type %s, but got null." this.Type.Name
25+
26+
member this.ReadAndDeserializeTyped(reader: Utf8JsonReader byref, options: JsonSerializerOptions) =
27+
reader.Read() |> ignore
28+
let res = JsonSerializer.Deserialize<'T>(&reader, options)
29+
this.CheckNull(res)
30+
res
31+
32+
[<AbstractClass>]
33+
type BaseJsonTupleConverter<'T>() =
1134
inherit JsonConverter<'T>()
1235

36+
abstract ReadCore: Utf8JsonReader byref * JsonSerializerOptions -> 'T
37+
abstract WriteCore: Utf8JsonWriter * 'T * JsonSerializerOptions -> unit
38+
39+
override this.Read(reader, typeToConvert, options) =
40+
expectAlreadyRead JsonTokenType.StartArray "array" &reader typeToConvert
41+
let res = this.ReadCore(&reader, options)
42+
readExpecting JsonTokenType.EndArray "end of array" &reader typeToConvert
43+
res
44+
45+
override this.Write(writer, value, options) =
46+
writer.WriteStartArray()
47+
this.WriteCore(writer, value, options)
48+
writer.WriteEndArray()
49+
50+
override _.HandleNull = true
51+
52+
type JsonTupleConverter<'T>(fsOptions: JsonFSharpOptions) =
53+
inherit BaseJsonTupleConverter<'T>()
54+
1355
let ty = typeof<'T>
1456
let fieldProps =
1557
FSharpType.GetTupleElements(ty)
16-
|> Array.map (fun t ->
17-
let tIsNullable = isNullableFieldType fsOptions t
18-
let needsNullChecking = not tIsNullable && not t.IsValueType
19-
{ Type = t; NeedsNullChecking = needsNullChecking }
20-
)
58+
|> Array.map (TupleProperty<obj>.MakeUntyped fsOptions.Record)
2159
let ctor = FSharpValue.PreComputeTupleConstructor(ty)
2260
let reader = FSharpValue.PreComputeTupleReader(ty)
2361

24-
override _.Read(reader, typeToConvert, options) =
25-
expectAlreadyRead JsonTokenType.StartArray "array" &reader typeToConvert
62+
override _.ReadCore(reader, options) =
2663
let elts = Array.zeroCreate fieldProps.Length
2764
for i in 0 .. fieldProps.Length - 1 do
2865
let p = fieldProps[i]
2966
reader.Read() |> ignore
3067
let value = JsonSerializer.Deserialize(&reader, p.Type, options)
31-
if p.NeedsNullChecking && isNull (box value) then
32-
failf "Unexpected null inside tuple-array. Expected type %s, but got null." p.Type.Name
68+
p.CheckNull value
3369
elts[i] <- value
34-
readExpecting JsonTokenType.EndArray "end of array" &reader typeToConvert
3570
ctor elts :?> 'T
3671

37-
override _.Write(writer, value, options) =
38-
writer.WriteStartArray()
72+
override _.WriteCore(writer, value, options) =
3973
let values = reader value
4074
for i in 0 .. fieldProps.Length - 1 do
4175
JsonSerializer.Serialize(writer, values[i], fieldProps[i].Type, options)
42-
writer.WriteEndArray()
4376

44-
override _.HandleNull = true
77+
type JsonTupleConverter<'T1, 'T2>(fsOptions: JsonFSharpOptions) =
78+
inherit BaseJsonTupleConverter<'T1 * 'T2>()
79+
80+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
81+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
82+
83+
override this.ReadCore(reader, options) =
84+
(p1.ReadAndDeserializeTyped(&reader, options), p2.ReadAndDeserializeTyped(&reader, options))
85+
86+
override this.WriteCore(writer, (x1, x2), options) =
87+
JsonSerializer.Serialize(writer, x1, options)
88+
JsonSerializer.Serialize(writer, x2, options)
89+
90+
type JsonStructTupleConverter<'T1, 'T2>(fsOptions: JsonFSharpOptions) =
91+
inherit BaseJsonTupleConverter<struct ('T1 * 'T2)>()
92+
93+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
94+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
95+
96+
override this.ReadCore(reader, options) =
97+
(p1.ReadAndDeserializeTyped(&reader, options), p2.ReadAndDeserializeTyped(&reader, options))
98+
99+
override this.WriteCore(writer, (x1, x2), options) =
100+
JsonSerializer.Serialize(writer, x1, options)
101+
JsonSerializer.Serialize(writer, x2, options)
102+
103+
type JsonTupleConverter<'T1, 'T2, 'T3>(fsOptions: JsonFSharpOptions) =
104+
inherit BaseJsonTupleConverter<'T1 * 'T2 * 'T3>()
105+
106+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
107+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
108+
let p3 = TupleProperty<'T3>.MakeTyped fsOptions.Record
109+
110+
override this.ReadCore(reader, options) =
111+
(p1.ReadAndDeserializeTyped(&reader, options),
112+
p2.ReadAndDeserializeTyped(&reader, options),
113+
p3.ReadAndDeserializeTyped(&reader, options))
114+
115+
override this.WriteCore(writer, (x1, x2, x3), options) =
116+
JsonSerializer.Serialize(writer, x1, options)
117+
JsonSerializer.Serialize(writer, x2, options)
118+
JsonSerializer.Serialize(writer, x3, options)
119+
120+
type JsonStructTupleConverter<'T1, 'T2, 'T3>(fsOptions: JsonFSharpOptions) =
121+
inherit BaseJsonTupleConverter<struct ('T1 * 'T2 * 'T3)>()
122+
123+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
124+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
125+
let p3 = TupleProperty<'T3>.MakeTyped fsOptions.Record
126+
127+
override this.ReadCore(reader, options) =
128+
(p1.ReadAndDeserializeTyped(&reader, options),
129+
p2.ReadAndDeserializeTyped(&reader, options),
130+
p3.ReadAndDeserializeTyped(&reader, options))
131+
132+
override this.WriteCore(writer, (x1, x2, x3), options) =
133+
JsonSerializer.Serialize(writer, x1, options)
134+
JsonSerializer.Serialize(writer, x2, options)
135+
JsonSerializer.Serialize(writer, x3, options)
136+
137+
type JsonTupleConverter<'T1, 'T2, 'T3, 'T4>(fsOptions: JsonFSharpOptions) =
138+
inherit BaseJsonTupleConverter<'T1 * 'T2 * 'T3 * 'T4>()
139+
140+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
141+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
142+
let p3 = TupleProperty<'T3>.MakeTyped fsOptions.Record
143+
let p4 = TupleProperty<'T4>.MakeTyped fsOptions.Record
144+
145+
override this.ReadCore(reader, options) =
146+
(p1.ReadAndDeserializeTyped(&reader, options),
147+
p2.ReadAndDeserializeTyped(&reader, options),
148+
p3.ReadAndDeserializeTyped(&reader, options),
149+
p4.ReadAndDeserializeTyped(&reader, options))
150+
151+
override this.WriteCore(writer, (x1, x2, x3, x4), options) =
152+
JsonSerializer.Serialize(writer, x1, options)
153+
JsonSerializer.Serialize(writer, x2, options)
154+
JsonSerializer.Serialize(writer, x3, options)
155+
JsonSerializer.Serialize(writer, x4, options)
156+
157+
type JsonStructTupleConverter<'T1, 'T2, 'T3, 'T4>(fsOptions: JsonFSharpOptions) =
158+
inherit BaseJsonTupleConverter<struct ('T1 * 'T2 * 'T3 * 'T4)>()
159+
160+
let p1 = TupleProperty<'T1>.MakeTyped fsOptions.Record
161+
let p2 = TupleProperty<'T2>.MakeTyped fsOptions.Record
162+
let p3 = TupleProperty<'T3>.MakeTyped fsOptions.Record
163+
let p4 = TupleProperty<'T4>.MakeTyped fsOptions.Record
164+
165+
override this.ReadCore(reader, options) =
166+
(p1.ReadAndDeserializeTyped(&reader, options),
167+
p2.ReadAndDeserializeTyped(&reader, options),
168+
p3.ReadAndDeserializeTyped(&reader, options),
169+
p4.ReadAndDeserializeTyped(&reader, options))
170+
171+
override this.WriteCore(writer, (x1, x2, x3, x4), options) =
172+
JsonSerializer.Serialize(writer, x1, options)
173+
JsonSerializer.Serialize(writer, x2, options)
174+
JsonSerializer.Serialize(writer, x3, options)
175+
JsonSerializer.Serialize(writer, x4, options)
45176

46-
new(fsOptions: JsonFSharpOptions) = JsonTupleConverter<'T>(fsOptions.Record)
47177

48-
type JsonTupleConverter(fsOptions) =
178+
type JsonTupleConverter(fsOptions, [<Optional>] forceGeneric) =
49179
inherit JsonConverterFactory()
50180

51181
static member internal CanConvert(typeToConvert: Type) =
52182
TypeCache.isTuple typeToConvert
53183

54-
static member internal CreateConverter(typeToConvert: Type, fsOptions: JsonFSharpOptions) =
55-
typedefof<JsonTupleConverter<_>>
56-
.MakeGenericType([| typeToConvert |])
57-
.GetConstructor([| typeof<JsonFSharpOptions> |])
58-
.Invoke([| fsOptions |])
59-
:?> JsonConverter
184+
static member internal CreateConverter
185+
(typeToConvert: Type, fsOptions: JsonFSharpOptions, [<Optional>] forceGeneric: bool)
186+
=
187+
let converterType =
188+
if forceGeneric then
189+
typedefof<JsonTupleConverter<_>>.MakeGenericType(typeToConvert)
190+
else
191+
let targs = typeToConvert.GetGenericArguments()
192+
let isStruct =
193+
typeToConvert.GetGenericTypeDefinition().Name.StartsWith("ValueTuple")
194+
match targs.Length, isStruct with
195+
| 2, false -> typedefof<JsonTupleConverter<_, _>>.MakeGenericType(targs)
196+
| 3, false -> typedefof<JsonTupleConverter<_, _, _>>.MakeGenericType(targs)
197+
| 4, false -> typedefof<JsonTupleConverter<_, _, _, _>>.MakeGenericType(targs)
198+
| 2, true -> typedefof<JsonStructTupleConverter<_, _>>.MakeGenericType(targs)
199+
| 3, true -> typedefof<JsonStructTupleConverter<_, _, _>>.MakeGenericType(targs)
200+
| 4, true -> typedefof<JsonStructTupleConverter<_, _, _, _>>.MakeGenericType(targs)
201+
| _ -> typedefof<JsonTupleConverter<_>>.MakeGenericType(typeToConvert)
202+
converterType.GetConstructor([| typeof<JsonFSharpOptions> |]).Invoke([| fsOptions |]) :?> JsonConverter
60203

61204
override _.CanConvert(typeToConvert) =
62205
JsonTupleConverter.CanConvert(typeToConvert)
63206

64207
override _.CreateConverter(typeToConvert, _options) =
65-
JsonTupleConverter.CreateConverter(typeToConvert, fsOptions)
208+
JsonTupleConverter.CreateConverter(typeToConvert, fsOptions, forceGeneric)

0 commit comments

Comments
 (0)