Haskell-way of modeling a type with dynamic JSON fields?

北城余情 提交于 2019-12-05 17:48:17

It depends on what you mean by arbitrary data. I'm going to extract what I think is a reasonable and non-trivial definition of "data contains an arbitrary document type" and show you a couple of possibilities.

First I'll point to a past blog post of mine. This demonstrates how to parse documents that vary in structure or nature. Existing example here: http://bitemyapp.com/posts/2014-04-17-parsing-nondeterministic-data-with-aeson-and-sum-types.html

As applied to your data type, this could look something like:

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)

Next I'll show you to define parameterizable structure with the use of a higher kinded type. Existing example here: 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)

With the above code we've parameterized data and made User a higher kinded type. Now User has structured parameterized by the types of its type arguments. The data field can now be a document such as with User CustomData, a string User Text or a number User Int. You probably want a semantically meaningful type, not Int/String. Use newtype as necessary to accomplish this.

For a rather worked up example of how to lend structure and meaning to a data type that many would otherwise encode as (Double, Double), see https://github.com/NICTA/coordinate.

You can combine these approaches if you think it appropriate. It depends partly on whether you want your type to be able to express a particular, single, possibility in the type argument to the enclosing document or not.

I have a ton of JSON processing code and examples of how to structure data in my library at https://github.com/bitemyapp/bloodhound

The guiding principle is to make invalid data unrepresentable via the types to the extent possible. Consider using "smart constructors" when types alone can't validate your data.

See more about smart constructors here: https://www.haskell.org/haskellwiki/Smart_constructors

If you really wanted to accept a fully arbitrary JSON substructure with Aeson's FromJSON class, I'd advise that you create a field user :: Value, which is Aeson's generic type for any JSON value. If you find possible types of this JSON value later, you may convert it using FromJSON again, but initially it will hold anything that is there.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!