fsprojects / FSharp.Data

F# Data: Library for Data Access
https://fsprojects.github.io/FSharp.Data
Other
817 stars 287 forks source link

FSharp.Data.Json serializes some floats as ints #1356

Open pbiggar opened 3 years ago

pbiggar commented 3 years ago

The float 100.0 gets serialized as 100, not as "100.0". This throws away the fact that it's a float, and violates the downstream contracts which expect a float here. AFAICT, there is no way to make it print as a float.

To reproduce in fsi:

> FSharp.Data.JsonValue.Parse("100.0").ToString();;
val it : string = "100.0"

> FSharp.Data.JsonValue.Float(100.0).ToString();;
val it : string = "100"
jcmrva commented 2 years ago

AsFloat() in FSharp.Data.JsonExtensions will give you a float, but I'm not sure FSharp.Data is the only issue:

>  JsonValue.Float(100.0).AsFloat();;
val it: float = 100.0

> string 100.0;;
val it: string = "100"

>  JsonValue.Float(100.0).AsFloat().ToString();;
val it: string = "100"

It does seem to parse correctly, but comes through as a decimal unless you create it as a float:

> JsonValue.Parse("""{"flt":100.0}""") |> fun j -> j?flt |> function | JsonValue.Number dec -> true | _ -> false;;
val it: bool = true

>  JsonValue.Float(100.0) |> function | JsonValue.Float fl -> true | _ -> false;;
val it: bool = true

I guess it makes sense from a .NET perspective but kind of wrong because JSON doesn't have multiple number types.

bongjunj commented 1 year ago

Float is a fallback of Number

https://github.com/fsprojects/FSharp.Data/blob/bbe54edc63a2d1ca3616f3b847ee34c0a88f91f2/src/FSharp.Data.Json.Core/JsonValue.fs#L34-L46

Please read the comment above.

FSharp.Data parses a number into a decimal value first, but if it fails, it parses the number into a float value. This is because the range of a decimal value can represent is limited.

> let n1 = FSharp.Data.JsonValue.Parse("100.0");;                                                                 
val n1: FSharp.Data.JsonValue = 100.0

> let n2 = FSharp.Data.JsonValue.Parse("123432472368476238462472002348732428734628462746823.034343343433343343434333433");;
val n2: FSharp.Data.JsonValue = 1.2343247236847624E+50

> n1.GetType().FullName;;
val it: string = "FSharp.Data.JsonValue+Number"

> n2.GetType().FullName;;
val it: string = "FSharp.Data.JsonValue+Float"

Therefore, basically, the problem is that ToString method of System.Decimal is not working as expected.

> decimal 100.0;;
val it: decimal = 100M {Scale = 0uy;}

> it.ToString ();;
val it: string = "100"

Solutions

To fix the problem you described, I think we should