使用动态 JSON 字段对类型进行建模的 Haskell 方式?

2024-03-27

我是 Haskell 的新手,来自命令式编程背景。我希望能够以“Haskell 方式”将对象序列化为 JSON,但还不太确定如何做到这一点。

我读过了RealWorldHaskell 第 5 章 http://book.realworldhaskell.org/read/writing-a-library-working-with-json-data.html其中讨论了一点 JSON,并使用了 Aeson。我还查看了一些用 Haskell 编写的 JSON API 库,例如:

  • Haskell 中的 Facebook API https://github.com/prowdsponsor/fb
  • Haskell 中的 Stripe API https://github.com/michaelschade/hs-stripe

这让我能够从对象创建非常基本的 JSON 字符串(也感谢这篇博文 http://no-fucking-idea.com/blog/2014/03/23/shortest-way-to-work-with-json-in-haskell/):

{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

data User = User {
  email :: String,
  name :: String
} deriving (Show, Generic)

instance ToJSON User

main = do
  let user = User "[email protected] /cdn-cgi/l/email-protection" "Hello World"
  let json = encode user
  putStrLn $ show json

这将打印出:

"{\"email\":\"[email protected] /cdn-cgi/l/email-protection",\"name\":\"Hello World\"}"

现在的目标是,向User可以具有任意字段的实例。 Facebook Graph API 有一个名为data,这是一个 JSON 对象,具有您想要的任何属性。例如,您可以向 Facebook 的 API 发出这样的请求(伪代码,不太熟悉 Facebook API):

POST api.facebook.com/actions
{
  "name": "read",
  "object": "book",
  "data": {
    "favoriteChapter": 10,
    "hardcover": true
  }
}

前两个字段,name, and object是类型String,而data字段是任意属性的映射。

问题是,实现这一目标的“Haskell 方式”是什么?User上面的型号?

我可以理解如何做这个简单的案例:

data User = User {
  email :: String,
  name :: String,
  data :: CustomData
} deriving (Show, Generic)

data CustomData = CustomData {
  favoriteColor :: String
}

但这并不完全是我想要的。这意味着User类型,当序列化为 JSON 时,将始终如下所示:

{
  "email": "",
  "name": "",
  "data": {
    "favoriteColor": ""
  }
}

问题是,你如何做到这一点,所以你只需要定义它User输入一次,然后可以附加任意字段data属性,同时仍然受益于静态类型(或任何接近静态类型的东西,但还不太熟悉类型的细节)。


这取决于您所说的任意数据的含义。我将提取我认为合理且重要的“数据包含任意文档类型”的定义,并向您展示几种可能性。

首先,我将指出我过去的一篇博客文章。这演示了如何解析结构或性质不同的文档。现有示例在这里:http://bitemyapp.com/posts/2014-04-17-parsing-nondeterministic-data-with-aeson-and-sum-types.html http://bitemyapp.com/posts/2014-04-17-parsing-nondeterministic-data-with-aeson-and-sum-types.html

当应用于您的数据类型时,这可能类似于:

data CustomData = NotesData Text | UserAge Int deriving (Show, Generic)
newtype Email = Email Text deriving (Show, Generic)
newtype Name  = Name  Text deriving (Show, Generic)

data User = User {
  email :: Email,
  name  :: Name,
  data  :: CustomData
} deriving (Show, Generic)

接下来,我将向您展示如何使用更高种类的类型来定义可参数化的结构。现有示例在这里:http://bitemyapp.com/posts/2014-04-11-aeson-and-user-created-types.html http://bitemyapp.com/posts/2014-04-11-aeson-and-user-created-types.html

newtype Email = Email Text deriving (Show, Generic)
newtype Name  = Name  Text deriving (Show, Generic)

-- 'a' needs to implement ToJSON/FromJSON as appropriate
data User a = User {
  email :: Email,
  name  :: Name,
  data  :: a
} deriving (Show, Generic)

通过上面的代码我们已经参数化了data并做了User一种更高级的类型。现在User已通过其类型参数的类型进行结构化参数化。这data字段现在可以是一个文档,例如User CustomData, 一个字符串User Text或一个数字User Int。您可能想要一个语义上有意义的类型,而不是 Int/String。根据需要使用 newtype 来完成此任务。

有关如何为数据类型提供结构和含义的相当复杂的示例,许多人会编码为(Double,Double),请参阅https://github.com/NICTA/坐标 https://github.com/NICTA/coordinate.

如果您认为合适,您可以组合这些方法。这部分取决于您是否希望您的类型能够在封闭文档的类型参数中表达特定的、单一的可能性。

我的库中有大量 JSON 处理代码和如何构建数据的示例,网址为https://github.com/bitemyapp/bloodhound https://github.com/bitemyapp/bloodhound

指导原则是尽可能通过类型使无效数据无法表示。当类型本身无法验证数据时,请考虑使用“智能构造函数”。

在此处查看有关智能构造函数的更多信息:https://www.haskell.org/haskellwiki/Smart_constructors https://www.haskell.org/haskellwiki/Smart_constructors

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用动态 JSON 字段对类型进行建模的 Haskell 方式? 的相关文章

随机推荐