Haskell使用aeson反序列化键值对JSON时FromJSON实例编写问题
适配顶层键值对映射JSON的解析方案
你已经编写的单个Person类型的FromJSON逻辑完全可以复用,核心点是区分开单个实体的解析逻辑和顶层JSON结构的解析逻辑:FromJSON Person实例只负责解析包含name/job/id三个字段的单个Person对象,不管这个对象出现在数组里、Map值位置还是某个固定字段下,这套逻辑都不需要修改。
首先先修正你原有代码里的笔误:FromJSON实例的类型名首字母需要大写,正确的单个Person解析实例如下:
import Data.Aeson import Data.Aeson.Types (withObject) import Data.ByteString.Lazy (ByteString) import Data.Map (Map) import qualified Data.Map as M import qualified Data.ByteString.Lazy as B data Person = Person { name :: String , job :: String , id :: Int } deriving Show -- 单个Person的解析逻辑,无需修改 instance FromJSON Person where parseJSON = withObject "Person" $ \obj -> Person <$> obj .: "name" <*> obj .: "job" <*> obj .: "id"
最简方案:直接复用aeson内置的Map解析能力
aeson默认已经为Map String v/HashMap Text v类型实现了FromJSON实例,会自动把JSON对象的所有键值对解析为Map的键值对,值部分会自动调用你写好的Person解析逻辑,不需要额外写任何自定义实例。
- 如果你需要保留外层的键名(比如
Person1/Person2这类标识),直接把解析目标指定为Map String Person即可:-- 直接解析为键到Person的映射 parsePersonMap :: ByteString -> Either String (Map String Person) parsePersonMap = eitherDecode - 如果你不需要保留外层键名,只想拿到所有Person的列表,直接对解析出的Map取元素即可:
parsePersonList :: ByteString -> Either String [Person] parsePersonList bs = M.elems <$> eitherDecode bs
可选方案:定义专属顶层类型
如果你希望用独立类型标记这类顶层映射结构,也可以自定义新类型后编写对应的FromJSON实例:
newtype PersonCollection = PersonCollection (Map String Person) deriving Show instance FromJSON PersonCollection where parseJSON = withObject "PersonCollection" $ \topObj -> PersonCollection <$> parseJSON (Object topObj)
补充说明:你之前提到的带
People数组的JSON结构,本质也是顶层固定结构+内部Person对象的组合,正确解析逻辑同样是先处理顶层结构,再复用单个Person的解析能力,不需要重写Person的解析逻辑:newtype PeopleListWrapper = PeopleListWrapper [Person] deriving Show instance FromJSON PeopleListWrapper where parseJSON = withObject "PeopleWrapper" $ \obj -> PeopleListWrapper <$> obj .: "People"
内容的提问来源于stack exchange,提问作者Kemp Jenning
相关产品推荐
相关产品推荐

