haskellghcaeson

Aeson 2 malformed field in cross-module import when Strict pragma is enabled


Please note that the issue described below doesn't happen in aeson 1.4.7 (stack LTS-16.31). This could be something related to ghc 9.2.7 perhaps.

I have noticed that if I define a simple record in aeson and then import it, the record "code" tag is now malformed as "body" in encode output. It happens only when importing as a module.

First, a simple module Test1:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell,DeriveGeneric #-}
{-# LANGUAGE Strict #-}

module Test1

where

import Data.Aeson.TH
import Data.Aeson
import GHC.Generics
import qualified Data.Text as T (Text)

data Rsp = Rsp { code::Int, tag :: T.Text, body:: T.Text } deriving (Show,Eq,Ord)
deriveJSON defaultOptions '' Rsp

Now, if I import the module in ghci and encode Rsp - "code" tag is now encoded as "body" tag which appears twice instead of once:

ghci> import Test1
ghci> import Data.Aeson (encode)
ghci> encode $ Rsp (1::Int) "nyi" ""
"{\"body\":1,\"tag\":\"nyi\",\"body\":\"\"}"

I found this when debugging migration issues for migrating from aeson 1.4.7.1 (on ghc 8.8.4 via Stack LTS-16.31) to aeson 2.0.3.0 (on ghc 9.2.7 via Stack LTS-20.13). It doesn't happen if instead of importing the module, I directly load Test1.hs code in ghci.

If I remove Strict pragma, the issue seems to go away. Perhaps something is going on here that is new to ghc 9.2+ or template haskell derivation in aeson?


Solution

  • This is a bug that is already fixed in 2.1.1.0 as noted in changelog:

    Use unsafeDupablePerformIO instead of incorrect accursedUnutterablePerformIO in creation of keys in TH serialisation. This fixes a bug in TH deriving, e.g. when Strict pragma was enabled.

    So it seems any LTS that has aeson pre 2.1.1.0 is unusable if template haskell derivation is enabled for aeson which I suspect is the case for lot of us.