-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathRecord.fs
122 lines (104 loc) · 4.27 KB
/
Record.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
namespace System.Text.Json.Serialization
open System
open System.Collections.Generic
open System.Text.Json
open FSharp.Reflection
open System.Text.Json.Serialization.Helpers
type internal RecordProperty =
{
Name: string
Type: Type
Ignore: bool
}
type JsonRecordConverter<'T>(options: JsonSerializerOptions) =
inherit JsonConverter<'T>()
let fieldProps =
FSharpType.GetRecordFields(typeof<'T>, true)
|> Array.map (fun p ->
let name =
match p.GetCustomAttributes(typeof<JsonPropertyNameAttribute>, true) with
| [| :? JsonPropertyNameAttribute as name |] -> name.Name
| _ ->
match options.PropertyNamingPolicy with
| null -> p.Name
| policy -> policy.ConvertName p.Name
let ignore =
p.GetCustomAttributes(typeof<JsonIgnoreAttribute>, true)
|> Array.isEmpty
|> not
{ Name = name; Type = p.PropertyType; Ignore = ignore }
)
let fieldCount = fieldProps.Length
let expectedFieldCount =
fieldProps
|> Seq.filter (fun p -> not p.Ignore)
|> Seq.length
let ctor = FSharpValue.PreComputeRecordConstructor(typeof<'T>, true)
let dector = FSharpValue.PreComputeRecordReader(typeof<'T>, true)
let propertiesByName =
if options.PropertyNameCaseInsensitive then
let d = Dictionary(StringComparer.OrdinalIgnoreCase)
fieldProps |> Array.iteri (fun i f ->
if not f.Ignore then
d.[f.Name] <- struct (i, f))
ValueSome d
else
ValueNone
let fieldIndex (reader: byref<Utf8JsonReader>) =
match propertiesByName with
| ValueNone ->
let mutable found = ValueNone
let mutable i = 0
while found.IsNone && i < fieldCount do
let p = fieldProps.[i]
if reader.ValueTextEquals(p.Name) then
found <- ValueSome (struct (i, p))
else
i <- i + 1
found
| ValueSome d ->
match d.TryGetValue(reader.GetString()) with
| true, p -> ValueSome p
| false, _ -> ValueNone
override _.Read(reader, typeToConvert, options) =
expectAlreadyRead JsonTokenType.StartObject "JSON object" &reader typeToConvert
let fields = Array.zeroCreate fieldCount
let mutable cont = true
let mutable fieldsFound = 0
while cont && reader.Read() do
match reader.TokenType with
| JsonTokenType.EndObject ->
cont <- false
| JsonTokenType.PropertyName ->
match fieldIndex &reader with
| ValueSome (i, p) when not p.Ignore ->
fieldsFound <- fieldsFound + 1
fields.[i] <- JsonSerializer.Deserialize(&reader, p.Type, options)
| _ ->
reader.Skip()
| _ -> ()
if fieldsFound < expectedFieldCount && not options.IgnoreNullValues then
raise (JsonException("Missing field for record type " + typeToConvert.FullName))
ctor fields :?> 'T
override _.Write(writer, value, options) =
writer.WriteStartObject()
(fieldProps, dector value)
||> Array.iter2 (fun p v ->
if not p.Ignore && not (options.IgnoreNullValues && isNull v) then
writer.WritePropertyName(p.Name)
JsonSerializer.Serialize(writer, v, options))
writer.WriteEndObject()
type JsonRecordConverter() =
inherit JsonConverterFactory()
static member internal CanConvert(typeToConvert) =
TypeCache.isRecord typeToConvert
static member internal CreateConverter(typeToConvert, options: JsonSerializerOptions) =
typedefof<JsonRecordConverter<_>>
.MakeGenericType([|typeToConvert|])
.GetConstructor([|typeof<JsonSerializerOptions>|])
.Invoke([|options|])
:?> JsonConverter
override _.CanConvert(typeToConvert) =
JsonRecordConverter.CanConvert(typeToConvert)
override _.CreateConverter(typeToConvert, options) =
JsonRecordConverter.CreateConverter(typeToConvert, options)