Search code examples
haskellaesonhspec

Issue with Aeson or Wai.JSON QuasiQuoter -- Converts 0.0 to 0


I am using Test.Hspec.Wai.JSON to check the return value of my api endpoints. I noticed that whenever i create a json with a value of 0.0, when the test runs, it converts it to 0 (Int) and if the api returns 0.0, the test fails.

let  j = [json|{"test":0.0}|]
request "GET" "some_url" [("Content-Type", "application/json")] ""
        `shouldRespondWith` j {matchStatus = 200}

   body mismatch:
     expected: {"test":0}  ---> this is the issue (0.0 has become 0)
     but got:  {"test":0.0} 

I am not that advanced in Haskell to figure out where in the library code this is happening. I looked at the source code for Test.Hspec.Wai.JSON and it seems to be relying on Aeson.QQ so not really sure the source of the issue. This is Test.Hspec.Wai.JSON source and this is the Aeson.QQ Source

Due to this, my work around has been to write a FromJSON instance to parse the entire response and check against the filled out record. This is a bit tedious.

Any suggestions as to what in the library code is causing this? And how to fix it?

Thanks,


Solution

  • The suspect appears to be this line of Data.Aeson.QQ:

    toExp (JsonNumber n) = [|Number (fromRational $(return $ LitE $ RationalL (toRational n)))|]
    

    It converts the number to a Rational, converts that to a Haskell expression, and then has the resulting expression then turn it back into a Number. This ends up discarding the fact that it was 0.0 rather than 0.

    This would not normally be a problem, as Aeson correctly defines == to make Numbers with equal values equal. This becomes an actual problem with Test.Hspec.Wai.JSON; the way it works is that it encodes the object back into a ByteString and expects it to match it exactly.

    While Data.Aeson.QQ is the root cause of your problem, I would not hold it to blame. Instead, Test.Hspec.Wai.JSON should not be serializing JSON objects and expecting their representations to be equivalent. Rather, it should be deserializing the actual response and comparing the decoded objects for equality. (After all, floating-point/integer isn’t the only possible problem. It also wouldn’t be able to handle object keys being reordered.) I’m not familiar with Hspec, so I’m not sure how you’d make it do that.