JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.8k stars 3.26k forks source link

TypeConverter works, but not JsonConverter (from dictionary keys) #1465

Open knocte opened 7 years ago

knocte commented 7 years ago

Source/destination types

    let Construct<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
        Microsoft.FSharp.Reflection.FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T
    let GetUnionCaseInfoAndInstance<'T> (caseInfo: Microsoft.FSharp.Reflection.UnionCaseInfo) =
        (Construct<'T> caseInfo)
    let GetAllElementsFromDiscriminatedUnion<'T>() =
        Microsoft.FSharp.Reflection.FSharpType.GetUnionCases(typeof<'T>)
        |> Seq.map GetUnionCaseInfoAndInstance<'T>

    [<JsonConverter(typeof<MyStringTypeConverter>)>]
    type DiscUnion =
        | D1
        | D2
        override self.ToString() =
            sprintf "%A" self
        static member GetAll(): seq<DiscUnion> =
            GetAllElementsFromDiscriminatedUnion<DiscUnion>()

    and private MyStringTypeConverter() =
        inherit JsonConverter()

        override this.CanConvert(objectType): bool =
            objectType = typedefof<DiscUnion>

        override this.ReadJson(reader: JsonReader, objectType: Type, existingValue: Object, serializer: JsonSerializer) =
            if (reader.TokenType = JsonToken.Null) then
                null
            else
                let token =
                    Newtonsoft.Json.Linq.JToken.Load(reader)
                          // not sure about the below way to convert to string, in stackoverflow it was a C# cast
                          .ToString()
                try
                    DiscUnion.GetAll().First(fun discUnion -> discUnion.ToString() = token) :> Object
                with ex -> raise(new Exception(sprintf "DiscUnion case not found: %s" token, ex))

        override this.WriteJson(writer: JsonWriter, value: Object, serializer: JsonSerializer) =
            let discUnion = value :?> DiscUnion
            writer.WriteValue(discUnion.ToString())

    type Foo =
        {
            Bar: int;
            Baz: Map<DiscUnion,int>;
        }

Source/destination JSON

{ "Bar": 42, "Baz":{"D1": 4242, "D2": 424242 }}

Expected behavior

Deserialize JSON properly into Foo object (like it would do if I had used TypeConverter instead of JsonConverter).

Actual behavior

Exception:

----> Newtonsoft.Json.JsonSerializationException : Could not convert string 'D1' to dictionary key type 'FSharpTests.Deserialization+DiscUnion'. Create a TypeConverter to convert from the string to the key type object. Path 'Value.Baz.D1', line 1, position 82. ----> Newtonsoft.Json.JsonSerializationException : Error converting value "D1" to type 'FSharpTests.Deserialization+DiscUnion'. Path 'Value.Baz.D1', line 1, position 82.

Steps to reproduce

JsonConvert.DeserializeObject<Foo>(json)
qbast commented 7 years ago

Possibly a duplicate of https://github.com/JamesNK/Newtonsoft.Json/issues/1371