我有一个代表持久记录的类型。我想要一个非常相似的类型来表示应该发布以创建新记录的数据。
这是完整的类型:
data Record = Reading
{ id: UUID
, value: String
...
}
“新”类型与减去“id”相同,该“id”将由数据库自动生成。我如何定义这个类型?我正在使用servant来定义API。
我当前的策略是在类型和所有字段前加上“new”前缀,这有效,但对于多字段模型来说是多余的。我还看到了嵌套策略,其中我有一个共同的共享类型。我也考虑过将 id 设为可选,但我真的不想发布它。
您可以使用类似“更高种类的数据”的方法来实现此目的。 首先,一些进口:
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Example where
import Data.Functor.Identity
import Data.Proxy
import Data.UUID
import Data.Aeson
import GHC.Generics
然后,定义具有更高种类类型参数的记录:
data Record f = Record { recordId :: f UUID, recordValue :: String } deriving (Generic)
Identity
Functor
为您提供此记录的一个变体,它始终具有一个 Id。
type RecordWithId = Record Identity
使用
Maybe
为您提供一个带有
可选id 的变体。
type RecordWithOptionalId = Record Maybe
可以用作具有单个无趣“单位”值的函子。 (并且没有包装类型的值)。这让我们可以为没有 ID 的
Record
创建一个类型。type RecordWithoutId = Record (Proxy)
我们可以为我们的
Show
导出
Record
。deriving instance (Show (f UUID)) => Show (Record f)
需要在
omitNothingFields = True
实例中传递
allowOmitedFields = True
和
Aeson
才能按照您的预期解析 RecordWithoutId
。这确实需要 Aeson >= 2.2.0.0 的版本(在撰写本文时该版本比最新的 Stackage Snapshot 更新)。如果此版本绑定不适合您,您可以手动实现 Aeson 实例。
instance (ToJSON (f UUID)) => ToJSON (Record f) where
toJSON = genericToJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }
instance (FromJSON (f UUID)) => FromJSON (Record f) where
parseJSON = genericParseJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }
用 ID 对值进行编码:ghci> BL.putStrLn $ encode (Record {recordId = Identity nil, recordValue = "value" })
{"recordId":"00000000-0000-0000-0000-000000000000","recordValue":"value"}
对没有 ID 的值进行编码:
ghci> BL.putStrLn $ encode (Record {recordId = Proxy, recordValue = "value" })
{"recordValue":"value"}
解码没有 ID 的值
ghci> decode "{\"recordValue\":\"value\"}" :: Maybe RecordWithoutId
Just (Record {recordId = Proxy, recordValue = "value"})