我使用的是返回JSON响应的外部API。其中一个响应是一个对象数组,这些对象由其中的字段值标识。我很难理解如何使用Aeson解析此类JSON响应。
以下是我的问题的简化版本:
newtype Content = Content { content :: [Media] } deriving (Generic)
instance FromJSON Content
data Media =
Video { objectClass :: Text
, title :: Text } |
AudioBook { objectClass :: Text
, title :: Text }
在API文档中,据说该对象可以通过字段 objectClass 来识别,该字段具有值" video"我们的视频对象和#34;有声读物"我们的 AudioBook 等等。示例JSON:
[{objectClass: "video", title: "Some title"}
,{objectClass: "audiobook", title: "Other title"}]
问题是如何使用Aeson来接近这种类型的JSON?
instance FromJSON Media where
parseJSON (Object x) = ???
答案 0 :(得分:9)
你基本上需要一个函数Text -> Text -> Media
:
toMedia :: Text -> Text -> Media
toMedia "video" = Video "video"
toMedia "audiobook" = AudioBook "audiobook"
FromJSON
实例现在非常简单(使用来自<$>
的{{1}}和<*>
):
Control.Applicative
但是,此时您还是多余的:instance FromJSON Media where
parseJSON (Object x) = toMedia <$> x .: "objectClass" <*> x .: "title"
或objectClass
中的Video
字段并未提供比实际类型更多的信息,因此您可能会删除它:
Audio
另请注意,data Media = Video { title :: Text }
| AudioBook { title :: Text }
toMedia :: Text -> Text -> Media
toMedia "video" = Video
toMedia "audiobook" = AudioBook
是部分的。您可能希望捕获无效的toMedia
值:
"objectClass"
最后,但并非最不重要的是,请记住valid JSON使用字符串作为名称。
答案 1 :(得分:3)
数据类型的默认转换,如:
data Media = Video { title :: Text }
| AudioBook { title :: Text }
deriving Generic
实际上非常接近你想要的。 (为了简化我的示例,我定义ToJSON
个实例并对示例进行编码以查看我们获得的JSON类型。)
因此,使用默认实例(查看the complete source file生成此输出):
[{"tag":"Video","title":"Some title"},{"tag":"AudioBook","title":"Other title"}]
让我们看看我们是否可以更接近自定义选项...
tagFieldName
使用自定义options:
mediaJSONOptions :: Options
mediaJSONOptions =
defaultOptions{ sumEncoding =
TaggedObject{ tagFieldName = "objectClass"
-- , contentsFieldName = undefined
}
}
instance ToJSON Media
where toJSON = genericToJSON mediaJSONOptions
我们得到:
[{"objectClass":"Video","title":"Some title"},{"objectClass":"AudioBook","title":"Other title"}]
(想想你想用真实代码中的未定义字段做什么。)
constructorTagModifier
添加
, constructorTagModifier = fmap Char.toLower
到mediaJSONOptions
给出:
[{"objectClass":"video","title":"Some title"},{"objectClass":"audiobook","title":"Other title"}]
太棒了!正是您指定的内容!
只需添加一个具有相同选项的实例即可从此格式进行解码:
instance FromJSON Media
where parseJSON = genericParseJSON mediaJSONOptions
示例:
*Main> encode example
"[{\"objectClass\":\"video\",\"title\":\"Some title\"},{\"objectClass\":\"audiobook\",\"title\":\"Other title\"}]"
*Main> decode $ fromString "[{\"objectClass\":\"video\",\"title\":\"Some title\"},{\"objectClass\":\"audiobook\",\"title\":\"Other title\"}]" :: Maybe [Media]
Just [Video {title = "Some title"},AudioBook {title = "Other title"}]
*Main>
为了获得更完整的图片,让我们看一下generic-aeson
包会给出的内容(hackage)。它还有很好的默认翻译,在某些方面与aeson
的翻译不同。
否则
import Generics.Generic.Aeson -- from generic-aeson package
并定义:
instance ToJSON Media
where toJSON = gtoJson
给出了结果:
[{"video":{"title":"Some title"}},{"audioBook":{"title":"Other title"}}]
因此,它与我们在使用aeson
时所看到的完全不同。
generic-aeson的选项(Settings)对我们来说并不感兴趣(它们只允许删除前缀)。
除了下限 - 构造函数名称的第一个字母外,generic-aeson
的翻译类似于aeson
中提供的选项:
让我们试试这个:
mediaJSONOptions =
defaultOptions{ sumEncoding = ObjectWithSingleField
, constructorTagModifier = fmap Char.toLower
}
是的,结果是:
[{"video":{"title":"Some title"}},{"audiobook":{"title":"Other title"}}]
TwoElemArray
)上面考虑了一个sumEncoding
TwoElemArray
可用的option,因为它给出了一个与所询问的JSON表示不太相似的数组。它是[["video",{"title":"Some title"}],["audiobook",{"title":"Other title"}]]
。例如:
mediaJSONOptions =
defaultOptions{ sumEncoding = TwoElemArray
, constructorTagModifier = fmap Char.toLower
}
由:
给出{{1}}