Skip to content

Commit 5919191

Browse files
committed
Feedback updates/integration
1 parent 39a32ff commit 5919191

File tree

2 files changed

+14
-20
lines changed

2 files changed

+14
-20
lines changed
Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
namespace FsCodec.SystemTextJson
22

33
open System
4-
open System.Linq.Expressions
54
open System.Text.Json
65
open System.Text.Json.Serialization
76

87
type RejectNullConverter<'T>() =
98
inherit System.Text.Json.Serialization.JsonConverter<'T>()
109

11-
static let defaultConverter = JsonSerializerOptions.Default.GetConverter(typeof<'T>) :?> JsonConverter<'T>
10+
let defaultConverter = JsonSerializerOptions.Default.GetConverter(typeof<'T>) :?> JsonConverter<'T>
1211
let msg () = sprintf "Expected value, got null. When rejectNull is true you must explicitly wrap optional %s values in an 'option'" typeof<'T>.Name
1312

1413
override _.HandleNull = true
1514

1615
override _.Read(reader, typeToConvert, options) =
1716
if reader.TokenType = JsonTokenType.Null then msg () |> nullArg else
17+
// PROBLEM: Fails with NRE when RejectNullConverter delegates to Default list<int> Converter
18+
// System.NullReferenceException
19+
// at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
20+
// https://github.com/dotnet/runtime/issues/50205 via https://github.com/jet/FsCodec/pull/112#issuecomment-1907633798
1821
defaultConverter.Read(&reader, typeToConvert, options)
1922
// Pretty sure the above is the correct approach (and this unsurprisingly loops, blowing the stack)
2023
// JsonSerializer.Deserialize(&reader, typeToConvert, options) :?> 'T
@@ -26,17 +29,8 @@ type RejectNullConverter<'T>() =
2629

2730
type RejectNullConverterFactory(predicate) =
2831
inherit JsonConverterFactory()
29-
new() =
30-
RejectNullConverterFactory(fun (t: Type) ->
31-
t.IsGenericType
32-
&& let gtd = t.GetGenericTypeDefinition() in gtd = typedefof<Set<_>> || gtd = typedefof<list<_>>)
33-
override _.CanConvert(t: Type) = predicate t
34-
35-
override _.CreateConverter(t, _options) =
36-
let openConverterType = typedefof<RejectNullConverter<_>>
37-
let constructor = openConverterType.MakeGenericType(t).GetConstructors() |> Array.head
38-
let newExpression = Expression.New(constructor)
39-
let lambda = Expression.Lambda(typeof<ConverterActivator>, newExpression)
32+
static let isListOrSet (t: Type) = t.IsGenericType && let g = t.GetGenericTypeDefinition() in g = typedefof<Set<_>> || g = typedefof<list<_>>
33+
new() = RejectNullConverterFactory(isListOrSet)
4034

41-
let activator = lambda.Compile() :?> ConverterActivator
42-
activator.Invoke()
35+
override _.CanConvert(t: Type) = predicate t
36+
override _.CreateConverter(t, _options) = typedefof<RejectNullConverter<_>>.MakeGenericType(t).GetConstructors().[0].Invoke[||] :?> _

tests/FsCodec.SystemTextJson.Tests/SerdesTests.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,25 @@ let [<Fact>] ``RejectNullConverter rejects null lists and Sets`` () =
9090
// if x.Kind <> JsonTypeInfoKind.Object then
9191
for p in x.Properties do
9292
let pt = p.PropertyType
93-
if pt.IsGenericType && (let gtd = pt.GetGenericTypeDefinition() in gtd = typedefof<list<_>> || gtd = typedefof<Set<_>>) then
93+
if pt.IsGenericType && (let d = pt.GetGenericTypeDefinition() in d = typedefof<list<_>> || d = typedefof<Set<_>>) then
9494
p.IsRequired <- true)
9595
let serdes = Options.Create(TypeInfoResolver = tir) |> Serdes
9696
raises<exn> <@ serdes.Deserialize<WithList> """{"x":0}""" @>
9797
#else
9898
let serdes = Options.Create(rejectNull = true) |> Serdes
9999

100-
// Fails with NRE when RejectNullConverter delegates to Default list<int> Converter
101-
// seems akin to https://github.com/dotnet/runtime/issues/86483
100+
// PROBLEM: Fails with NRE when RejectNullConverter delegates to Default list<int> Converter
102101
// let res = serdes.Deserialize<WithList> """{"x":0,"y":[1]}"""
103102
// test <@ [1] = res.y @>
104103

105-
// PROBLEM: Doesn't raise
104+
// PROBLEM: Doesn't raise as Converter not called
106105
raises<exn> <@ serdes.Deserialize<WithList> """{"x":0}""" @>
106+
107107
// PROBLEM: doesnt raise - there doesn't seem to be a way to intercept explicitly passed nulls
108108
// raises<JsonException> <@ serdes.Deserialize<WithList> """{"x":0,"y":null}""" @>
109109
#endif
110110

111-
#if false // I guess TypeShape is doing a reasaonable thing not propagating
111+
#if false // I guess TypeShape is doing a reasonable thing not propagating
112112
// PROBLEM: TypeShape.Generic.exists does not call the predicate if the list or set is `null`
113113
let res = serdes.Deserialize<WithList> """{"x":0}"""
114114
let hasNullList = TypeShape.Generic.exists (fun (x: int list) -> obj.ReferenceEquals(x, null))

0 commit comments

Comments
 (0)