Skip to content

Commit 6587bb5

Browse files
committed
Implement record serialization with Reflection.Emit
1 parent 758de38 commit 6587bb5

5 files changed

Lines changed: 77 additions & 27 deletions

File tree

benchmarks/FSharp.SystemTextJson.Benchmarks/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ArrayTestBase<'t>(instance: 't) =
3434
options
3535

3636

37-
[<Params(10,100)>]
37+
[<Params(10,100,1000)>]
3838
member val ArrayLength = 0 with get, set
3939

4040
member val InstanceArray = [||] with get, set

src/FSharp.SystemTextJson/FSharp.SystemTextJson.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<Compile Include="TypeCache.fs" />
8+
<Compile Include="RecordField.fs" />
89
<Compile Include="Record.fs" />
910
<Compile Include="Union.fs" />
1011
<Compile Include="All.fs" />

src/FSharp.SystemTextJson/Record.fs

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

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

7-
type internal RecordProperty =
8-
{
9-
Name: string
10-
Type: Type
11-
Ignore: bool
12-
}
13-
148
type JsonRecordConverter<'T>() =
159
inherit JsonConverter<'T>()
1610

17-
static let fieldProps =
18-
FSharpType.GetRecordFields(typeof<'T>, true)
19-
|> Array.map (fun p ->
20-
let name =
21-
match p.GetCustomAttributes(typeof<JsonPropertyNameAttribute>, true) with
22-
| [| :? JsonPropertyNameAttribute as name |] -> name.Name
23-
| _ -> p.Name
24-
let ignore =
25-
p.GetCustomAttributes(typeof<JsonIgnoreAttribute>, true)
26-
|> Array.isEmpty
27-
|> not
28-
{ Name = name; Type = p.PropertyType; Ignore = ignore }
29-
)
11+
static let fieldProps = RecordField<'T>.properties()
3012

3113
static let expectedFieldCount =
3214
fieldProps
@@ -35,8 +17,6 @@ type JsonRecordConverter<'T>() =
3517

3618
static let ctor = FSharpValue.PreComputeRecordConstructor(typeof<'T>, true)
3719

38-
static let dector = FSharpValue.PreComputeRecordReader(typeof<'T>, true)
39-
4020
static let fieldIndex (reader: byref<Utf8JsonReader>) =
4121
let mutable found = ValueNone
4222
let mutable i = 0
@@ -74,11 +54,11 @@ type JsonRecordConverter<'T>() =
7454

7555
override __.Write(writer, value, options) =
7656
writer.WriteStartObject()
77-
(fieldProps, dector value)
78-
||> Array.iter2 (fun p v ->
57+
fieldProps
58+
|> Array.iter (fun p ->
7959
if not p.Ignore then
8060
writer.WritePropertyName(p.Name)
81-
JsonSerializer.Serialize(writer, v, options))
61+
p.Serialize.Invoke(writer, value, options))
8262
writer.WriteEndObject()
8363

8464
type JsonRecordConverter() =
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
namespace System.Text.Json.Serialization
2+
3+
open System
4+
open System.Reflection
5+
open FSharp.Reflection
6+
open System.Reflection.Emit
7+
open System.Text.Json
8+
9+
type internal Serializer<'Record> = delegate of Utf8JsonWriter * 'Record * JsonSerializerOptions -> unit
10+
type internal FieldReader<'Record, 'Field> = delegate of 'Record -> 'Field
11+
12+
type internal RecordField<'Record> =
13+
{
14+
Name: string
15+
Type: Type
16+
Ignore: bool
17+
Serialize: Serializer<'Record>
18+
}
19+
20+
static member name (p: PropertyInfo) =
21+
match p.GetCustomAttributes(typeof<JsonPropertyNameAttribute>, true) with
22+
| [| :? JsonPropertyNameAttribute as name |] -> name.Name
23+
| _ -> p.Name
24+
25+
static member isIgnore (p: PropertyInfo) =
26+
p.GetCustomAttributes(typeof<JsonIgnoreAttribute>, true)
27+
|> Array.isEmpty
28+
|> not
29+
30+
static member serializer<'Field> (f: FieldInfo) =
31+
let getter =
32+
let dynMethod =
33+
new DynamicMethod(
34+
f.Name,
35+
f.FieldType,
36+
[| typeof<obj> |],
37+
typedefof<RecordField<_>>.Module,
38+
skipVisibility = true
39+
)
40+
let gen = dynMethod.GetILGenerator()
41+
gen.Emit(OpCodes.Ldarg_0)
42+
gen.Emit(OpCodes.Ldfld, f)
43+
gen.Emit(OpCodes.Ret)
44+
dynMethod.CreateDelegate(typeof<FieldReader<'Record, 'Field>>) :?> FieldReader<'Record, 'Field>
45+
Serializer<'Record>(fun writer r options ->
46+
let v = getter.Invoke(r)
47+
JsonSerializer.Serialize<'Field>(writer, v, options)
48+
)
49+
50+
static member properties () =
51+
let recordTy = typeof<'Record>
52+
let fields = recordTy.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic)
53+
let props = FSharpType.GetRecordFields(recordTy, true)
54+
(fields, props)
55+
||> Array.map2 (fun f p ->
56+
let serializer =
57+
typeof<RecordField<'Record>>.GetMethod("serializer", BindingFlags.Static ||| BindingFlags.NonPublic)
58+
.MakeGenericMethod(p.PropertyType)
59+
.Invoke(null, [|f|])
60+
:?> Serializer<'Record>
61+
{
62+
Name = RecordField<'Record>.name p
63+
Type = p.PropertyType
64+
Ignore = RecordField<'Record>.isIgnore p
65+
Serialize = serializer
66+
} : RecordField<'Record>
67+
)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
FSharp.Core
2-
System.Text.Json
2+
System.Text.Json
3+
System.Reflection.Emit.Lightweight
4+
System.Reflection.Emit.ILGeneration

0 commit comments

Comments
 (0)