Tarmil / FSharp.SystemTextJson

System.Text.Json extensions for F# types
MIT License
329 stars 43 forks source link

JsonConverterAttribute applied to a record field is ignored #148

Open kyryloboiev opened 1 year ago

kyryloboiev commented 1 year ago

I have a Robot type with a string property Model I get an json that has a Model type with Name and Version for this I create a ModelConverter and expect to process and return string

module Tests

open System
open System.Text.Json
open System.Text.Json.Serialization
open Xunit

[<JsonFSharpConverter>]
type Model = { Name:string; Version:string }

[<Sealed>]
type ModelConverter ()  =
    inherit JsonConverter<string> ()
    override __.CanConvert(t) = t = typeof<Model>
    override __.Write(_, _, _) = raise (NotImplementedException())
    override __.Read (reader, _, _) =
        match reader.TokenType with
        | JsonTokenType.Number -> reader.GetInt32() |> string
        | JsonTokenType.String -> reader.GetString()
        | JsonTokenType.StartObject ->
            let result = JsonSerializer.Deserialize<{| Name:string; Version:string|}>(&reader)
            result.Name + result.Version
        | _ -> raise <| JsonException("Unexpected token type")

type Robot = {
     Code : int
     [<JsonConverter(typeof<ModelConverter >)>]
     Model : string
}

let r2d2 = { Code = 123; Model = "R2D2v1.0" } }

let options = JsonSerializerOptions()
options.Converters.Add(ModelConverter ())

[<Fact>]
let ``**should return string** Model`` () =
    Assert.Equal(r2d2, JsonSerializer.Deserialize<Robot>( """{"Code":123,"Model":{"Name":"R2D2","Version":"v1.0"}}""", options))


System.InvalidOperationException
The converter specified on 'Tests+Robot.Model' is not compatible with the type 'System.String'.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(Type classTypeAttributeIsOn, MemberInfo memberInfo, Type typeToConvert)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetConverterFromAttribute(JsonConverterAttribute converterAttribute, Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetCustomConverterForMember(Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1.CreateProperty(Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, Boolean shouldCheckForRequiredKeyword)
   at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1.CacheMember(Type typeToConvert, MemberInfo memberInfo, Boolean& propertyOrderSpecified, Dictionary`2& ignoredMembers, Boolean shouldCheckForRequiredKeyword)
   at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1.LateAddProperties()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.InitializePropertyCache()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureLocked|143_0()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Boolean resolveIfMutable)
   at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions options, Type inputType)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Tests.should return number code() in C:\TestProject1\Tests.fs:line 36
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)```
xperiandri commented 1 year ago

@Tarmil do you agree that this has to work?

mishun commented 1 year ago

I've tested the code above a bit, and it seems that it works if you replace CanConvert with:

override __.CanConvert(t) = t = typeof<string>

at least since version 1.0.7 (which, judging from date in nuget was actual when this issue was raised)

mishun commented 1 year ago

Also peculiar fact: if you add [<JsonFSharpConverter>] attribute to Robot record then it still works under version 1.0.7, but throws

Unhandled exception. System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1.

under 1.1.23 and later versions, which may be a bug.

xperiandri commented 1 year ago

@Tarmil can this be fixed? I tried to figure out myself how to do that but had no luck. Or maybe you at least suggest what to do?