Parsing 是否可以将一个不断变化的JSON键与一个在较大记录类型中包含aeson的sum类型数据构造函数相匹配?

Parsing 是否可以将一个不断变化的JSON键与一个在较大记录类型中包含aeson的sum类型数据构造函数相匹配?,parsing,haskell,aeson,Parsing,Haskell,Aeson,因此,我有一个数据类型ItemType,它使用其数据构造函数名称进行解码(请参阅FromJSON实例) 导入数据.Aeson 导入Data.Aeson.Types 导入Data.Char(toLower) 进口GHC.仿制药 数据项类型= MkLogin登录 |明信片 |MkIdentity身份 |MkSecureNote注释 派生(通用、显示) 小写::字符串->字符串 小写“=” 小写字母(s:ss)=toLower s:ss stripPrefix::String->String stri

因此,我有一个数据类型
ItemType
,它使用其数据构造函数名称进行解码(请参阅FromJSON实例)

导入数据.Aeson
导入Data.Aeson.Types
导入Data.Char(toLower)
进口GHC.仿制药
数据项类型=
MkLogin登录
|明信片
|MkIdentity身份
|MkSecureNote注释
派生(通用、显示)
小写::字符串->字符串
小写“=”
小写字母(s:ss)=toLower s:ss
stripPrefix::String->String
stripPrefix('M':'k':ss)=ss
stripPrefix str=str
--|使用ItemType数据构造函数名称解码值
实例FromJSON ItemType,其中
parseJSON=genericParseJSON默认选项
{constructorTagModifier=小写.stripPrefix
,sumcodencing=ObjectWithSingleField}
我想做的是将这个类型作为字段添加到一个更大的记录类型中,该记录类型名为
Item

数据项=
项{u对象::字符串
,_id::String
,_organizationId::可能是Int
,_folderId::可能是Int
,_类型::Int
,_name::String
,_注释::字符串
你最喜欢的是什么
,???::ItemType--不知道如何在没有其他字段名的情况下添加此项
,_collectionId::[Int]
,_修订日期::可能是字符串
}派生(通用、显示)
实例FromJSON项,其中
parseJSON=
genericParseJSON默认选项{fieldLabelModifier=Strip下划线}
但是,我不想为该类型创建新的字段名。相反,我想使用aeson在
ItemType
上匹配的数据构造函数作为字段名,因为我试图根据
ItemType
是什么来建模JSON对象中
ItemType
字段的键。因此,在本例中,密钥是“登录”、“卡”、“身份”、“安全注意”。也许我应该使用
TaggedObject
进行
sumcodeding
,但我不完全确定它是如何工作的


对象的JSON列表示例:。在这里,您可以通过键“login”、“card”、“identity”查看
ItemType
字段,具体取决于它们是什么类型。

一种方法是在
数据声明中没有
ItemType
字段。然后使用元组或自定义对类型来保存这两个片段;因此:

data ItemWithType = ItemWithType ItemType Item

instance FromJSON ItemWithType where
    parseJSON v = liftA2 ItemWithType (parseJSON v) (parseJSON v)
您也可以跳过定义
ItemWithType
,只需使用

\o -> liftA2 (,) (parseJSON o) (parseJSON o)

直接解析具有一致名称的字段元组和变量键下的对象。

您可以使用一种相当难看的方法来预处理传入的JSON
,以便实际的JSON输入如下:

{
  "id": "foo",
  "bool": false
}
将其解析为:

{
  "id": "foo",
  "itemtype": {"bool" : false}
}
通用解析器可以使用
ObjectWithSingleField
sum编码方法直接处理

作为一个简化示例,给出:

data ItemType =
    MkInt Int
  | MkBool Bool
  deriving (Generic, Show)

instance FromJSON ItemType where
  parseJSON = genericParseJSON defaultOptions
    { constructorTagModifier = map toLower . \('M':'k':ss) -> ss
    , sumEncoding = ObjectWithSingleField }
以及:

您可以为
Item
编写一个
FromJSON
实例,该实例将
“int”
“bool”
字段嵌套在
“itemtype”
字段中。(原始字段的副本保留在原位,但被通用解析器忽略。)

实例FromJSON项,其中
parseJSON v=do
v'ss}v'
其中nest o=对象(HM.insert“itemtype”item纯o)
其中item=subObj“int”subObj“bool”fail“no item type field”
子对象k=(\v->object[(k,v)])o.:k
完整代码:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

import           Control.Applicative
import           Data.Aeson
import           Data.Aeson.Types
import           Data.Char (toLower)
import           GHC.Generics
import qualified Data.HashMap.Strict as HM

data ItemType =
    MkInt Int
  | MkBool Bool
  deriving (Generic, Show)

instance FromJSON ItemType where
  parseJSON = genericParseJSON defaultOptions
    { constructorTagModifier = map toLower . \('M':'k':ss) -> ss
    , sumEncoding = ObjectWithSingleField }

data Item =
  Item { _id :: String
       , _itemtype :: ItemType
       }
  deriving (Generic, Show)

instance FromJSON Item where
  parseJSON v = do
    v' <- withObject "Item" nest v
    genericParseJSON defaultOptions { fieldLabelModifier = \('_':ss) -> ss } v'
    where nest o = Object <$> (HM.insert "itemtype" <$> item <*> pure o)
            where item = subObj "int" <|> subObj "bool" <|> fail "no item type field"
                  subObj k = (\v -> object [(k,v)]) <$> o .: k

test1, test2, test3 :: Either String Item
test1 = eitherDecode "{\"id\":\"foo\",\"bool\":false}"
test2 = eitherDecode "{\"id\":\"foo\",\"int\":10}"
test3 = eitherDecode "{\"id\":\"foo\"}"

main = do
  print test1
  print test2
  print test3
{-#派生通用语言}
{-#语言重载字符串}
{-#语言元组{-}
导入控制
导入数据.Aeson
导入Data.Aeson.Types
导入Data.Char(toLower)
进口GHC.仿制药
导入符合条件的Data.HashMap.Strict作为HM
数据项类型=
MkInt
|MkBool Bool
派生(通用、显示)
实例FromJSON ItemType,其中
parseJSON=genericParseJSON默认选项
{constructorTagModifier=map-toLower.\('M':'k':ss)->ss
,sumcodencing=ObjectWithSingleField}
数据项=
项{u id::String
,_itemtype::itemtype
}
派生(通用、显示)
实例FromJSON项,其中
parseJSON v=do
v'ss}v'
其中nest o=对象(HM.insert“itemtype”item纯o)
其中item=subObj“int”subObj“bool”fail“no item type field”
子对象k=(\v->object[(k,v)])o.:k
test1、test2、test3::任意一个字符串项
test1=eitherDecode“{\“id\:\“foo\”,“bool\”:false}”
test2=eitherDecode“{\“id\:\“foo\”,“int\”:10}”
test3=eitherDecode“{\“id\:\“foo\”}”
main=do
打印测试1
打印测试2
打印测试3
不过,一般来说,除非你经常这样做,否则为了清晰易懂,最好还是放弃泛型,编写必要的样板。即使是你最初的例子,也没有那么繁重。是的,您必须保持类型和实例的同步,但是几个简单的测试应该可以发现任何问题。例如,类似于:

instance FromJSON Item where
  parseJSON = withObject "Item" $ \o ->
    Item <$> o .: "object"
         <*> o .: "id"
         <*> o .:? "organizationId"
         <*> o .:? "folderId"
         <*> o .: "type"
         <*> o .: "name"
         <*> o .: "notes"
         <*> o .: "favorite"
         <*> parseItemType o
         <*> o .: "collectionIds"
         <*> o .:? "revisionDate"
    where parseItemType o =
                MkLogin <$> o .: "login"
            <|> MkCard <$> o .: "card"
            <|> MkIdentity <$> o .: "identity"
            <|> MkSecureNote <$> o .: "securenote"
实例FromJSON项,其中
parseJSON=withObject“项”$\o->
项目o.:“对象”
o.:“id”
o.:?“组织ID”
o.:?“福尔德里德”
o.:“类型”
o.:“姓名”
o.:“注意事项”
o.:“最爱”
parseItemType o
o.:“集合ID”
o.:?“修订日期”
其中parseItemType为o=
MkLogin o.:“登录”
MkCard o.:“卡”
MKO标识:“标识”
MkSecureNote o.:“securenote”
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

import           Control.Applicative
import           Data.Aeson
import           Data.Aeson.Types
import           Data.Char (toLower)
import           GHC.Generics
import qualified Data.HashMap.Strict as HM

data ItemType =
    MkInt Int
  | MkBool Bool
  deriving (Generic, Show)

instance FromJSON ItemType where
  parseJSON = genericParseJSON defaultOptions
    { constructorTagModifier = map toLower . \('M':'k':ss) -> ss
    , sumEncoding = ObjectWithSingleField }

data Item =
  Item { _id :: String
       , _itemtype :: ItemType
       }
  deriving (Generic, Show)

instance FromJSON Item where
  parseJSON v = do
    v' <- withObject "Item" nest v
    genericParseJSON defaultOptions { fieldLabelModifier = \('_':ss) -> ss } v'
    where nest o = Object <$> (HM.insert "itemtype" <$> item <*> pure o)
            where item = subObj "int" <|> subObj "bool" <|> fail "no item type field"
                  subObj k = (\v -> object [(k,v)]) <$> o .: k

test1, test2, test3 :: Either String Item
test1 = eitherDecode "{\"id\":\"foo\",\"bool\":false}"
test2 = eitherDecode "{\"id\":\"foo\",\"int\":10}"
test3 = eitherDecode "{\"id\":\"foo\"}"

main = do
  print test1
  print test2
  print test3
instance FromJSON Item where
  parseJSON = withObject "Item" $ \o ->
    Item <$> o .: "object"
         <*> o .: "id"
         <*> o .:? "organizationId"
         <*> o .:? "folderId"
         <*> o .: "type"
         <*> o .: "name"
         <*> o .: "notes"
         <*> o .: "favorite"
         <*> parseItemType o
         <*> o .: "collectionIds"
         <*> o .:? "revisionDate"
    where parseItemType o =
                MkLogin <$> o .: "login"
            <|> MkCard <$> o .: "card"
            <|> MkIdentity <$> o .: "identity"
            <|> MkSecureNote <$> o .: "securenote"