Skip to content

Commit

Permalink
Implement struct record serialization with Reflection.Emit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmil committed Aug 10, 2019
1 parent 6587bb5 commit 8c0fa5d
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 13 deletions.
18 changes: 16 additions & 2 deletions benchmarks/FSharp.SystemTextJson.Benchmarks/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ type TestRecord =
thing: bool option
time: System.DateTimeOffset }

[<Struct>]
type TestStructRecord =
{ name: string
thing: bool voption
time: System.DateTimeOffset }

type SimpleClass() =
member val Name: string = null with get, set
member val Thing: bool option = None with get, set
Expand Down Expand Up @@ -58,7 +64,7 @@ type ArrayTestBase<'t>(instance: 't) =
[<Benchmark>]
member this.Deserialize_SystemTextJson () = System.Text.Json.JsonSerializer.Deserialize<'t[]>(this.Serialized, systemTextOptions)

let recordInstance =
let recordInstance : TestRecord =
{ name = "sample"
thing = Some true
time = System.DateTimeOffset.UnixEpoch.AddDays(200.) }
Expand All @@ -67,6 +73,14 @@ let recordInstance =
type Records () =
inherit ArrayTestBase<TestRecord>(recordInstance)

let recordStructInstance : TestStructRecord =
{ name = "sample"
thing = ValueSome true
time = System.DateTimeOffset.UnixEpoch.AddDays(200.) }

type StructRecords () =
inherit ArrayTestBase<TestStructRecord>(recordStructInstance)

type Classes() =
inherit ArrayTestBase<SimpleClass>(SimpleClass(Name = "sample", Thing = Some true, Time = DateTimeOffset.UnixEpoch.AddDays(200.)))

Expand Down Expand Up @@ -106,7 +120,7 @@ let config =
.With(ExecutionValidator.FailOnError)

let defaultSwitch () =
BenchmarkSwitcher([| typeof<Records>; typeof<Classes>; typeof<ReflectionComparison> |])
BenchmarkSwitcher([| typeof<Records>; typeof<StructRecords>; typeof<Classes>; typeof<ReflectionComparison> |])


[<EntryPoint>]
Expand Down
7 changes: 3 additions & 4 deletions src/FSharp.SystemTextJson/Record.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ open FSharp.Reflection
type JsonRecordConverter<'T>() =
inherit JsonConverter<'T>()

static let fieldProps = RecordField<'T>.properties()
static let fieldProps = RecordField<'T>.fields()

static let expectedFieldCount =
fieldProps
Expand Down Expand Up @@ -54,11 +54,10 @@ type JsonRecordConverter<'T>() =

override __.Write(writer, value, options) =
writer.WriteStartObject()
fieldProps
|> Array.iter (fun p ->
for p in fieldProps do
if not p.Ignore then
writer.WritePropertyName(p.Name)
p.Serialize.Invoke(writer, value, options))
p.Serialize.Invoke(writer, value, options)
writer.WriteEndObject()

type JsonRecordConverter() =
Expand Down
15 changes: 8 additions & 7 deletions src/FSharp.SystemTextJson/RecordField.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ open FSharp.Reflection
open System.Reflection.Emit
open System.Text.Json

type internal Serializer<'Record> = delegate of Utf8JsonWriter * 'Record * JsonSerializerOptions -> unit
type internal FieldReader<'Record, 'Field> = delegate of 'Record -> 'Field
type internal Serializer = Action<Utf8JsonWriter, obj, JsonSerializerOptions>

type internal RecordField<'Record> =
{
Name: string
Type: Type
Ignore: bool
Serialize: Serializer<'Record>
Serialize: Serializer
}

static member name (p: PropertyInfo) =
Expand All @@ -39,15 +38,17 @@ type internal RecordField<'Record> =
)
let gen = dynMethod.GetILGenerator()
gen.Emit(OpCodes.Ldarg_0)
if f.DeclaringType.IsValueType then
gen.Emit(OpCodes.Unbox, typeof<'Record>)
gen.Emit(OpCodes.Ldfld, f)
gen.Emit(OpCodes.Ret)
dynMethod.CreateDelegate(typeof<FieldReader<'Record, 'Field>>) :?> FieldReader<'Record, 'Field>
Serializer<'Record>(fun writer r options ->
dynMethod.CreateDelegate(typeof<Func<obj, 'Field>>) :?> Func<obj, 'Field>
Serializer(fun writer r options ->
let v = getter.Invoke(r)
JsonSerializer.Serialize<'Field>(writer, v, options)
)

static member properties () =
static member fields () =
let recordTy = typeof<'Record>
let fields = recordTy.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic)
let props = FSharpType.GetRecordFields(recordTy, true)
Expand All @@ -57,7 +58,7 @@ type internal RecordField<'Record> =
typeof<RecordField<'Record>>.GetMethod("serializer", BindingFlags.Static ||| BindingFlags.NonPublic)
.MakeGenericMethod(p.PropertyType)
.Invoke(null, [|f|])
:?> Serializer<'Record>
:?> Serializer
{
Name = RecordField<'Record>.name p
Type = p.PropertyType
Expand Down

0 comments on commit 8c0fa5d

Please sign in to comment.