我有以下数据类型:
data DocumentOrDirectory = Document DocumentName DocumentContent
| Directory DirectoryName [DocumentOrDirectory]
我附带了以下forJSON代码。它有效,但需要改进。它应该单独转换文档和目录,但我不知道该怎么做。
instance JSON.ToJSON DocumentOrDirectory where
toJSON (Document documentName documentContent) = JSON.object
[ "document" JSON..= JSON.object
[ "name" JSON..= (T.pack $ id documentName)
, "content" JSON..= (T.pack $ id documentContent)
]
]
toJSON (Directory dirName dirContent) = JSON.object
[ "directory" JSON..= JSON.object
[ "name" JSON..= (T.pack $ id dirName)
, "content" JSON..= JSON.toJSON dirContent
]
]
我需要能够从JSON解析DocumentOrDirectory对象。这就是我提出的(不起作用):
instance JSON.FromJSON DocumentOrDirectory where
parseJSON (Object v@(Document documentName documentContent)) =
DocumentOrDirectory <$> documentName .: "name"
<*> documentContent .: "content"
parseJSON (Object v@(Directory dirName dirContent) =
DocumentOrDirectory <$> dirName .: "name"
<*> dirContent .: "content"
parseJSON _ = mzero
如何修改现有代码以便能够将数据转换为JSON?
答案 0 :(得分:4)
让我们一步一步地解决这个问题。
首先,我假设为了示例,名称和内容只是String
:
type DirectoryName = String
type DocumentName = String
type DocumentContent = String
您提到要分别序列化Document
和Directory
。也许你也希望单独使用它们。让我们将它们分开:
data Document = Document DocumentName DocumentContent deriving Show
data Directory = Directory DirectoryName [DocumentOrDirectory] deriving Show
newtype DocumentOrDirectory = DocumentOrDirectory (Either Document Directory) deriving Show
现在DocumentOrDirectory
是类型别名或Either Document Directory
。我们使用newtype
,因为我们想为它编写自己的实例。默认Either
实例不会为我们工作。
然后定义一些辅助函数:
liftDocument :: Document -> DocumentOrDirectory
liftDocument = DocumentOrDirectory . Left
liftDirectory :: Directory -> DocumentOrDirectory
liftDirectory = DocumentOrDirectory . Right
通过这个定义,我们可以编写单独的ToJSON
实例:
instance ToJSON Document where
toJSON (Document name content) = object [ "document" .= object [
"name" .= name,
"content" .= content ]]
instance ToJSON Directory where
toJSON (Directory name content) = object [ "directory" .= object [
"name" .= name,
"content" .= content ]]
instance ToJSON DocumentOrDirectory where
toJSON (DocumentOrDirectory (Left d)) = toJSON d
toJSON (DocumentOrDirectory (Right d)) = toJSON d
我们应该检查Document
和Directory
是如何序列化的(我对JSON输出进行了美化):
*Main> let document = Document "docname" "lorem"
*Main> B.putStr (encode document)
{
"document": {
"content": "lorem",
"name": "docname"
}
}
*Main> let directory = Directory "dirname" [Left document, Left document]
*Main> B.putStr (encode directory) >> putChar '\n'
{
"directory": {
"content": [
{
"document": {
"content": "lorem",
"name": "docname"
}
},
{
"document": {
"content": "lorem",
"name": "docname"
}
}
],
"name": "directory"
}
}
B.putStr (encode $ liftDirectory directory)
会产生同样的结果!
下一步是编写解码器FromJSON
实例。我们看到密钥(directory
或document
)显示基础数据是Directory
还是Document
。因此,JSON格式是非重叠的(不显着),所以我们可以尝试解析Document
然后Directory
。
instance FromJSON Document where
parseJSON (Object v) = maybe mzero parser $ HashMap.lookup "document" v
where parser (Object v') = Document <$> v' .: "name"
<*> v' .: "content"
parser _ = mzero
parseJSON _ = mzero
instance FromJSON Directory where
parseJSON (Object v) = maybe mzero parser $ HashMap.lookup "directory" v
where parser (Object v') = Directory <$> v' .: "name"
<*> v' .: "content"
parser _ = mzero
parseJSON _ = mzero
instance FromJSON DocumentOrDirectory where
parseJSON json = (liftDocument <$> parseJSON json) <|> (liftDirectory <$> parseJSON json)
支票:
*Main> decode $ encode directory :: Maybe DocumentOrDirectory
Just (DocumentOrDirectory (Right (Directory "directory" [DocumentOrDirectory (Left (Document "docname" "lorem")),DocumentOrDirectory (Left (Document "docname" "lorem"))])))
我们可以在对象数据中使用 type tag 序列化数据,然后序列化和反序列化看起来会更好一些:
instance ToJSON Document where
toJSON (Document name content) = object [
"type" .= ("document" :: Text),
"name" .= name,
"content" .= content ]
生成的文件将是:
{
"type": "document",
"name": "docname",
"content": "lorem"
}
解码:
instance FromJSON Document where
-- We could have guard here
parseJSON (Object v) = Document <$> v .: "name"
<*> v .= "content"
instance FromJSON DocumentOrDirectory where
-- Here we check the type, and dynamically select appropriate subparser
parseJSON (Object v) = do typ <- v .= "type"
case typ of
"document" -> liftDocument $ parseJSON v
"directory" -> liftDirectory $ parseJSON v
_ -> mzero
在具有子类型的语言中,您可以使用这样的scala:
sealed trait DocumentOrDirectory
case class Document(name: String, content: String) extends DocumentOrDirectory
case class Directory(name: String, content: Seq[DocumentOrDirectory]) extends DocumentOrDirectory
有人可能会说这种方法(依赖于子类型)更方便。在Haskell中我们更明确:liftDocument
和liftDirectory
可以被认为是显式类型强制/ upcast,如果你想考虑对象。