Aeson 解码 JSON 可以是字符串或 int 的对象

Aeson decode JSON object that can be either a string or an int

我正在处理来自 REST 服务器的一些格式复杂的 JSON 响应。为了解码它们,我有几种数据类型来处理不同的嵌套对象。例如:

... Other types ...

data Profile =
  Profile { fields :: [KVPair]
  } deriving (Show)

instance FromJSON Profile where
  parseJSON (Object v) =
    Profile <$> v .: "Fields" 
  parseJSON _ = mzero

data KVPair =
  KVPair { key :: Int
         , value :: String
  } deriving (Show)

instance FromJSON KVPair where
  parseJSON (Object v) =
    KVPair <$> v .: "Key"
           <*> v .: "Value" 
  parseJSON _ = mzero

除了最终的 KVPair 类型外,一切正常。我的 JSON 对象都有整数键;但是,值可以是整数或字符串:

      {
        "Key": 0,
        "Value": "String Value!"
      },
      {
        "Key": 1,
        "Value": 42
      }

现在我想我可以向由 StringInt 组成的值解码添加另一种求和类型,但我宁愿避免为此添加一个全新的类型。 Aeson有没有简单的方法来处理这种情况?

您只需使用 Aeson Value 类型来处理具有可以是任何 JSON 值的字段的对象。

有两个简单的修复。一种是简单地写

data KVPair = KVPair { key :: Int, value :: Value }

并保持所有其他代码不变。消费者将需要检查 Value 以查看它是字符串 Y 还是数字 Y。

可能更好的方法是简单地提供两个都可以转换为所需格式的替代解析器。例如,保持 KVPair 定义不变,可以这样写

showInt :: Int -> String
showInt = show

instance FromJSON KVPair where
    parseJSON (Object v)
        =   KVPair
        <$> v .: "Key"
        <*> (v .: "Value" <|> (showInt <$> v .: "Value"))

两全其美的办法是将有关它是 String 还是 Int 的信息保留在 附近以拒绝其他类型的值;例如

data KVPair = KVPair { key :: Int, value :: Either String Int }

instance FromJSON KVPair where
    parseJSON (Object v)
        =   KVPair
        <$> v .: "Key"
        <*> (   (Left  <$> v .: "Value")
            <|> (Right <$> v .: "Value")
            )