Aeson解析异构数组字段返回Nothing问题及解决问询
解决Aeson解析异构JSON列表的问题
首先,咱们来拆解你遇到的问题:你的JSON里path是个异构数组——既有字符串(比如"networking"、"eth0"),又有数字(0),而你之前的代码里直接用fmap (V.! 2) (o .: "path"),这里o .: "path"会默认尝试把整个数组解析成Vector Text,但数组里的数字元素和Text类型不匹配,导致解析失败,最终返回Nothing。eitherDecode给出的报错expected Text, encountered Number也正好印证了这一点。
解决方案:把异构数组解析成Vector Value再处理
Aeson完全支持处理异构列表,核心思路是先把数组解析成保留所有原始类型的Vector Value(Aeson的Value类型可以表示任何JSON值),然后针对你需要的元素单独做类型转换。
修改你的FromJSON实例代码如下:
{-# LANGUAGE OverloadedStrings #-} import Control.Monad (mzero) import Data.Aeson import Data.ByteString.Lazy (ByteString) import Data.Text import qualified Data.Vector as V data HostNicIp = HostNicIp { hniHost :: !Text , hniNic :: !Text , hniIp :: !Text } deriving Show instance FromJSON HostNicIp where parseJSON (Object o) = do hniHost <- o .: "certname" -- 先把path解析成保留原始类型的Vector Value pathArr <- o .: "path" -- 取出索引为2的元素(也就是第3个元素),并转换为Text hniNic <- case pathArr V.!? 2 of Just val -> parseJSON val -- 用parseJSON把Value转换成Text Nothing -> mzero -- 如果数组长度不够,解析失败 hniIp <- o .: "value" return HostNicIp{..} parseJSON _ = mzero main :: IO () main = print ( decode demo :: Maybe HostNicIp ) demo :: ByteString demo = "{\"certname\":\"some.server.name\",\"path\":[\"networking\",\"interfaces\",\"eth0\",\"bindings\",0,\"address\"],\"name\":\"networking\",\"value\":\"192.168.1.1\",\"environment\":\"develop\"}"
代码说明
- 解析
path为Vector Value:o .: "path"在这里会返回Parser (Vector Value),因为Aeson无法推断出统一的元素类型,会默认保留每个元素的原始Value类型(字符串是String,数字是Number)。 - 安全取出目标元素:用
V.!?代替V.!可以避免数组越界导致的运行时错误,如果索引不存在就返回mzero让解析失败。 - 类型转换:用
parseJSON val把取出的Value转换成Text——因为你要的第3个元素是字符串,这个转换会成功;如果是数字元素,你可以用parseJSON val :: Parser Int来转换成整数类型。
验证结果
现在运行eitherDecode demo :: Either String HostNicIp,会得到:
Right (HostNicIp {hniHost = "some.server.name", hniNic = "eth0", hniIp = "192.168.1.1"})
这样就成功解决了异构数组的解析问题啦!
内容的提问来源于stack exchange,提问作者bongsun
相关产品推荐
相关产品推荐

