fsprojects / FSharp.Json

F# JSON Reflection based serialization library
Apache License 2.0
222 stars 26 forks source link

FSharp.Json.JsonDeserializationError: Expected type float is incompatible with jvalue: "13672.74" #6

Closed aggieben closed 6 years ago

aggieben commented 6 years ago

I'm trying to deserialize into a record somewhat like this:

type ApiResponse = {
    [<JsonField("A Name", Transform=typeof<FloatTransform>)>]
    value : float
}

where FloatTransform is defined as follows:

type FloatTransform() =
    interface ITypeTransform with
        member this.targetType() = typeof<string>
        member this.toTargetType obj =
            let value = unbox<float> obj
            value.ToString() :> obj
        member this.fromTargetType obj =
            match obj with
            | :? string as s -> Convert.ToDouble(s) |> box
            | _ -> raise (System.ArgumentException())

However, when I try to deserialize some json like this:

{
    "A Name": "13405.34"
}

by calling deserialize like this: Json.deserialize<ApiResponse> jsonString, it throws this exception:

FSharp.Json.JsonDeserializationError: Expected type float is incompatible with jvalue: "13672.74"
   at FSharp.Json.JsonValueHelpers.raiseWrongType[a,b](JsonPath path, String typeName, a jvalue)
   at FSharp.Json.Core.deserializeNonOption@298(JsonConfig config, JsonPath path, Type t, JsonField jsonField, JsonValue jvalue)
   at FSharp.Json.Core.deserializeRecord@423(JsonConfig config, JsonPath path, Type t, JsonValue jvalue)
   at FSharp.Json.Core.deserializeNonOption@298(JsonConfig config, JsonPath path, Type t, JsonField jsonField, JsonValue jvalue)
   at Microsoft.FSharp.Collections.ArrayModule.MapIndexed[T,TResult](FSharpFunc`2 mapping, T[] array)
   at FSharp.Json.Core.deserializeArray@388(JsonConfig config, JsonPath path, Type t, JsonValue jvalue)
   at FSharp.Json.Core.deserializeNonOption@298(JsonConfig config, JsonPath path, Type t, JsonField jsonField, JsonValue jvalue)
   at FSharp.Json.Core.deserializeRecord@423(JsonConfig config, JsonPath path, Type t, JsonValue jvalue)
   at FSharp.Json.Json.deserializeEx[T](JsonConfig config, String json)

I put breakpoints in my transform implementation and verified that way that the methods are being called, but I still wind up with this error. I also tried reversing the transform - in other words, I tried both float -> string and string -> float, but have this same problem either way. Why?

vsapronov commented 6 years ago

Hi Bill,

What version of FSharp.Json are you using?

Here are some things I noticed.

First, this line:

member this.targetType = typeof<string>

Should be:

member this.targetType () = typeof<string>

The targetType is a function...

Second thing: your json says "13405.34" and your error message says "13672.74". It tells me that the example code is not exactly the same as the code that is failing for you. I have quickly put together unit test based on your code and it just works: https://gist.github.com/vsapronov/0737745a294fd4eac0e60139c7b51fef The fix of targetType is the only thing that I have changed in your code.

aggieben commented 6 years ago

I'm using FSharp.Json version 0.3.1. The issue above with this.targetType above is just a typo (which I've edited now). As you have guessed, this isn't the full code, it's just the relevant part. I'll have a look at your gist and see if there's anything there that's different from what I'm doing.

aggieben commented 6 years ago

Ah! Naturally, this was just a stupid mistake. The real json I was working with has another float right next to the one I showed you, but it's actually a json number, not a string 🤦‍♂️

{
    "A Name": "13405.45",
    "Another Name": 13405.45
}

I had the transform set up the same way on both, and I just missed that they aren't the same types in the actual json (which is stupid, but upstream).

Thanks for looking at this.

vsapronov commented 6 years ago

Usually FSharp.Json gives a json path to the field that is causing error. In this case it would be "Another Name" - this should give a hint in what field you have an issue... Have you seen this json path in your Exception info? If not, then it could be potential for improvement. My goal was give json path to user all the time deserialization error happens...