ksa*_*jev 10 parsing json haskell aeson
我正在使用返回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 }
Run Code Online (Sandbox Code Playgroud)
在API文档中,可以说对象可以通过字段objectClass来识别,该字段对于我们的Video对象具有值"video"而对于我们的AudioBook具有 "有声读物" 等等.示例JSON:
[{objectClass: "video", title: "Some title"}
,{objectClass: "audiobook", title: "Other title"}]
Run Code Online (Sandbox Code Playgroud)
问题是如何使用Aeson来接近这种类型的JSON?
instance FromJSON Media where
parseJSON (Object x) = ???
Run Code Online (Sandbox Code Playgroud)
你基本上需要一个功能Text -> Text -> Media:
toMedia :: Text -> Text -> Media
toMedia "video" = Video "video"
toMedia "audiobook" = AudioBook "audiobook"
Run Code Online (Sandbox Code Playgroud)
该FromJSON实例现在非常简单(使用<$>和<*>来自 Control.Applicative):
instance FromJSON Media where
parseJSON (Object x) = toMedia <$> x .: "objectClass" <*> x .: "title"
Run Code Online (Sandbox Code Playgroud)
但是,此时您是多余的:objectClass字段中Video或者Audio没有提供比实际类型更多的信息,因此您可以将其删除:
data Media = Video { title :: Text }
| AudioBook { title :: Text }
toMedia :: Text -> Text -> Media
toMedia "video" = Video
toMedia "audiobook" = AudioBook
Run Code Online (Sandbox Code Playgroud)
另请注意,这toMedia是部分的.您可能希望捕获无效"objectClass"值:
instance FromJSON Media where
parseJSON (Object x) =
do oc <- x .: "objectClass"
case oc of
String "video" -> Video <$> x .: "title"
String "audiobook" -> AudioBook <$> x .: "title"
_ -> empty
{- an alternative using a proper toMedia
toMedia :: Alternative f => Text -> f (Text -> Media)
toMedia "video" = pure Video
toMedia "audiobook" = pure AudioBook
toMedia _ = empty
instance FromJSON Media where
parseJSON (Object x) = (x .: "objectClass" >>= toMedia) <*> x .: "title"
-}
Run Code Online (Sandbox Code Playgroud)
最后,但并非最不重要的是,请记住有效的JSON使用字符串作为名称.
数据类型的默认转换,如:
data Media = Video { title :: Text }
| AudioBook { title :: Text }
deriving Generic
Run Code Online (Sandbox Code Playgroud)
实际上非常接近你想要的。(为了我的示例的简单性,我定义了ToJSON实例并对示例进行了编码,以查看我们得到的 JSON 类型。)
因此,使用我们拥有的默认实例(查看生成此输出的完整源文件):
[{"tag":"Video","title":"Some title"},{"tag":"AudioBook","title":"Other title"}]
Run Code Online (Sandbox Code Playgroud)
让我们看看我们是否可以更接近自定义选项......
tagFieldName使用自定义选项:
mediaJSONOptions :: Options
mediaJSONOptions =
defaultOptions{ sumEncoding =
TaggedObject{ tagFieldName = "objectClass"
-- , contentsFieldName = undefined
}
}
instance ToJSON Media
where toJSON = genericToJSON mediaJSONOptions
Run Code Online (Sandbox Code Playgroud)
我们得到:
[{"objectClass":"Video","title":"Some title"},{"objectClass":"AudioBook","title":"Other title"}]
Run Code Online (Sandbox Code Playgroud)
(想想你想用实际代码中的未定义字段做什么。)
constructorTagModifier添加
, constructorTagModifier = fmap Char.toLower
Run Code Online (Sandbox Code Playgroud)
要mediaJSONOptions得到:
[{"objectClass":"video","title":"Some title"},{"objectClass":"audiobook","title":"Other title"}]
Run Code Online (Sandbox Code Playgroud)
伟大的!正是你指定的!
只需添加具有相同选项的实例即可从此格式解码:
instance FromJSON Media
where parseJSON = genericParseJSON mediaJSONOptions
Run Code Online (Sandbox Code Playgroud)
例子:
*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>
Run Code Online (Sandbox Code Playgroud)
为了获得更完整的图片,让我们也看看generic-aeson包会提供什么(在hackage)。它还具有很好的默认翻译,在某些方面与aeson.
正在做
import Generics.Generic.Aeson -- from generic-aeson package
Run Code Online (Sandbox Code Playgroud)
并定义:
instance ToJSON Media
where toJSON = gtoJson
Run Code Online (Sandbox Code Playgroud)
给出结果:
[{"video":{"title":"Some title"}},{"audioBook":{"title":"Other title"}}]
Run Code Online (Sandbox Code Playgroud)
因此,它与我们在使用aeson.
generic-aeson 的选项(Settings)对我们来说并不有趣(它们只允许去除前缀)。
(完整的源文件。)
除了小写构造函数名称的第一个字母外,generic-aeson的翻译似乎类似于 中可用的选项aeson:
让我们试试这个:
mediaJSONOptions =
defaultOptions{ sumEncoding = ObjectWithSingleField
, constructorTagModifier = fmap Char.toLower
}
Run Code Online (Sandbox Code Playgroud)
是的,结果是:
[{"video":{"title":"Some title"}},{"audiobook":{"title":"Other title"}}]
Run Code Online (Sandbox Code Playgroud)
TwoElemArray)一个可用的选项为sumEncoding已经从上面的考虑排除在外,因为它提供了一个数组,它是不是很类似JSON表示询问。它是TwoElemArray。例子:
[["video",{"title":"Some title"}],["audiobook",{"title":"Other title"}]]
Run Code Online (Sandbox Code Playgroud)
是(谁)给的:
mediaJSONOptions =
defaultOptions{ sumEncoding = TwoElemArray
, constructorTagModifier = fmap Char.toLower
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1017 次 |
| 最近记录: |