我需要处理一项服务的JSON,该服务有时会将字段的值 "123"
而不是 123
作为返回结果。虽然这很丑陋,但我无法更改该服务。有没有一种简单的方法来推导出可以处理它的 FromJSON
实例?使用 deriveJSON
派生的标准实例(https://hackage.haskell.org/package/aeson-1.5.4.1/docs/Data-Aeson-TH.html)无法解决这个问题。
我需要处理一项服务的JSON,该服务有时会将字段的值 "123"
而不是 123
作为返回结果。虽然这很丑陋,但我无法更改该服务。有没有一种简单的方法来推导出可以处理它的 FromJSON
实例?使用 deriveJSON
派生的标准实例(https://hackage.haskell.org/package/aeson-1.5.4.1/docs/Data-Aeson-TH.html)无法解决这个问题。
一个简单(虽然可能不太优雅)的选择是将属性定义为Aeson的Value
类型。这里有一个示例:
{-#LANGUAGE DeriveGeneric #-}
module Q65410397 where
import GHC.Generics
import Data.Aeson
data JExample = JExample { jproperty :: Value } deriving (Eq, Show, Generic)
instance ToJSON JExample where
instance FromJSON JExample where
Aeson可以解码带有数字的JSON值:
*Q65410397> decode "{\"jproperty\":123}" :: Maybe JExample
Just (JExample {jproperty = Number 123.0})
如果值是字符串,它也可以正常工作:
*Q65410397> decode "{\"jproperty\":\"123\"}" :: Maybe JExample
Just (JExample {jproperty = String "123"})
如果将属性定义为Value
,这意味着在Haskell端,它也可以容纳数组和其他对象,因此您应该至少在代码中有一个处理该情况的路径。如果您绝对确定第三方服务永远不会在那个位置给您提供一个数组,那么上述方法并不是最优雅的解决方案。
另一方面,如果它给出了123
和"123"
两种形式,那么这已经表明您可能不能信任合同的类型安全性...
FromJSON
实例,也许可以定义一个新类型覆盖Int
,并手工创建一个FromJSON
实例,仅用于处理那个奇怪的解析字段。{-# LANGUAGE TypeApplications #-}
import Control.Applicative
import Data.Aeson
import Data.Text
import Data.Text.Read (decimal)
newtype SpecialInt = SpecialInt { getSpecialInt :: Int } deriving (Show, Eq, Ord)
instance FromJSON SpecialInt where
parseJSON v =
let fromInt = parseJSON @Int v
fromStr = do
str <- parseJSON @Text v
case decimal str of
Right (i, _) -> pure i
Left errmsg -> fail errmsg
in SpecialInt <$> (fromInt <|> fromStr)
SpecialInt
字段的记录派生FromJSON
。FromJSON
实例而将字段设置为SpecialInt
似乎有些强行。"需要以奇怪的方式解析"是外部格式的属性,而不是领域的属性。
FromJSON
实例时,请将此字段视为SpecialInt
,但最终返回一个Int
”。也就是说,我们只想在反序列化时处理SpecialInt
。可以使用"generic-data-surgery"库来完成这项工作。{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data User = User { name :: String, age :: Int } deriving (Show,Generic)
假设我们想将“age”解析为SpecialInt
,我们可以这样做:
{-# LANGUAGE DataKinds #-}
import Generic.Data.Surgery (toOR', modifyRField, fromOR, Data)
instance FromJSON User where
parseJSON v = do
r <- genericParseJSON defaultOptions v
-- r is a synthetic Data which we must tweak in the OR and convert to User
let surgery = fromOR . modifyRField @"age" @1 getSpecialInt . toOR'
pure (surgery r)
{-# LANGUAGE OverloadedStrings #-}
main :: IO ()
main = do
print $ eitherDecode' @User $ "{ \"name\" : \"John\", \"age\" : \"123\" }"
print $ eitherDecode' @User $ "{ \"name\" : \"John\", \"age\" : 123 }"
Generic
表示来实现,因此该技术无法与使用Template Haskell生成的反序列化器配合使用。
deriveJSON
和FromJSON
类。 - Li-yao Xia