解析一个 aeson 对象的 "the rest"
Parsing "the rest" of an aeson object
出于某种原因,我无法全神贯注于 Aeson 中任意成功的解析,而不会使整个系统崩溃并导致 space 泄漏。
这是我的问题:
newtype Foo = Foo
{ getFoo :: [(String, Maybe String)]
} deriving (Show, Eq)
instance ToJSON Foo where
toJSON (Foo xs) = object $
map (\(k,mv) -> T.pack k .= mv) xs
到目前为止,对 Foo
进行编码非常好。但是,我想制作一个 拒绝 几个键的解析器,如果它们存在的话。现在,我正在进行伪拒绝,这就是为什么我认为我得到了一个糟糕的结果:
import qualified Data.HashMap as HM
-- the "duck-tape and chewing gum" approach
instance FromJSON Foo where
parseJSON (Object o) = Foo <$> parseJSON (Object theRest)
where
theRest = foldr HM.delete o [ "foo"
, "bar"
]
parseJSON _ = empty
这个版本让我认为操作内部对象是不正确的,因为解析器可能在解析器之外的 HashMap 中获取 "more" 数据(因为惰性字节串被输入它),但我显然不确定这一点。所以,我尝试了一种不同的方法:
instance FromJSON Foo where
parseJSON (Object o) =
(Foo . filter (\(k,_) -> k `elem` toIgnore)) <$>
parseJSON (Object o)
where
toIgnore = ["foo", "bar"]
parseJSON _ = empty
但是这个 也 似乎会导致死锁/space 泄漏(不确定要诊断这种执行停止的确切原因)。除了对象的几个键之外,什么是接受所有的建议方法?我需要在 (Object o)
结构上进行模式匹配,因为我正在为我的数据类型在不同的组件中手动查找 o .: "foo"
和 o .: "bar"
。理想情况下,我只想 从内容中删除 那些键并继续解析,因为我 已经考虑了它们 (因此 - "the rest").
还有希望吗?
对于您的 PartialAppContact 示例,这里有一个似乎有效的更普通的方法:
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
import Data.Aeson
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HM
import Control.Monad
import Text.Heredoc
type RequiredField = String
type OptionalField = String
data PartialAppContact = PartialAppContact
{ partialAppContactRequired :: [(RequiredField, String)]
, partialAppContactOptional :: [(OptionalField, Maybe String)]
} deriving (Show, Eq)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
reqPairs <- forM required $ \k -> do
v <- o .: k
s <- parseJSON v
return (T.unpack k, s)
nonReqPairs <- forM [ (k,v) | (k,v) <- HM.toList o, k `notElem` required ] $ \(k,v) -> do
s <- parseJSON v
return (T.unpack k, s)
return $ PartialAppContact reqPairs nonReqPairs
test1 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\"}" :: Either String PartialAppContact
input = [str|
| { "firstName": "a first name"
| , "lastName": "a last name"
| , "email": "asasd@asd.com"
| , "phoneNumber": "123-123-123"
| , "another field": "blah blah" }
|]
test2 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\" }" :: Either String PartialAppContact
test3 = Data.Aeson.eitherDecode input :: Either String PartialAppContact
更新
根据您的意见,考虑编写实例的思路:
import Data.List (partition)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
let (group1, group2) = partition (\(k,_) -> k `elem` required) (HM.toList o)
reqFields <- forM group1 $ \(k,v) -> do s <- parseJSON v; return (T.unpack k, s)
otherFields <- forM group2 (\(k,v) -> (T.unpack k,) <$> parseJSON v)
return $ PartialAppContact reqFields otherFields
我发现一个有效的实现需要使用 (.:?)
,才能正确实现 可选的已知 字段。从那里,您可以自由分解 HashMap 并重新 parseJSON
它的子字段:
instance FromJSON Foo where
parseJSON (Object o) = do
mfoo <- o .:? "foo"
mbar <- o .:? "bar"
let foundFields = catMaybes [mfoo, mbar]
rest <- mapM (\(k,v) -> (T.unpack k,) <$> parseJSON v)
(toList theRest)
return $ Foo rest -- assuming you're done with `foundFields`
where
theRest = foldr HM.delete o ["foo", "bar"]
要查看评论中讨论的问题的最终实现,see this commit。
出于某种原因,我无法全神贯注于 Aeson 中任意成功的解析,而不会使整个系统崩溃并导致 space 泄漏。
这是我的问题:
newtype Foo = Foo
{ getFoo :: [(String, Maybe String)]
} deriving (Show, Eq)
instance ToJSON Foo where
toJSON (Foo xs) = object $
map (\(k,mv) -> T.pack k .= mv) xs
到目前为止,对 Foo
进行编码非常好。但是,我想制作一个 拒绝 几个键的解析器,如果它们存在的话。现在,我正在进行伪拒绝,这就是为什么我认为我得到了一个糟糕的结果:
import qualified Data.HashMap as HM
-- the "duck-tape and chewing gum" approach
instance FromJSON Foo where
parseJSON (Object o) = Foo <$> parseJSON (Object theRest)
where
theRest = foldr HM.delete o [ "foo"
, "bar"
]
parseJSON _ = empty
这个版本让我认为操作内部对象是不正确的,因为解析器可能在解析器之外的 HashMap 中获取 "more" 数据(因为惰性字节串被输入它),但我显然不确定这一点。所以,我尝试了一种不同的方法:
instance FromJSON Foo where
parseJSON (Object o) =
(Foo . filter (\(k,_) -> k `elem` toIgnore)) <$>
parseJSON (Object o)
where
toIgnore = ["foo", "bar"]
parseJSON _ = empty
但是这个 也 似乎会导致死锁/space 泄漏(不确定要诊断这种执行停止的确切原因)。除了对象的几个键之外,什么是接受所有的建议方法?我需要在 (Object o)
结构上进行模式匹配,因为我正在为我的数据类型在不同的组件中手动查找 o .: "foo"
和 o .: "bar"
。理想情况下,我只想 从内容中删除 那些键并继续解析,因为我 已经考虑了它们 (因此 - "the rest").
还有希望吗?
对于您的 PartialAppContact 示例,这里有一个似乎有效的更普通的方法:
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
import Data.Aeson
import qualified Data.Text as T
import qualified Data.HashMap.Strict as HM
import Control.Monad
import Text.Heredoc
type RequiredField = String
type OptionalField = String
data PartialAppContact = PartialAppContact
{ partialAppContactRequired :: [(RequiredField, String)]
, partialAppContactOptional :: [(OptionalField, Maybe String)]
} deriving (Show, Eq)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
reqPairs <- forM required $ \k -> do
v <- o .: k
s <- parseJSON v
return (T.unpack k, s)
nonReqPairs <- forM [ (k,v) | (k,v) <- HM.toList o, k `notElem` required ] $ \(k,v) -> do
s <- parseJSON v
return (T.unpack k, s)
return $ PartialAppContact reqPairs nonReqPairs
test1 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\"}" :: Either String PartialAppContact
input = [str|
| { "firstName": "a first name"
| , "lastName": "a last name"
| , "email": "asasd@asd.com"
| , "phoneNumber": "123-123-123"
| , "another field": "blah blah" }
|]
test2 = Data.Aeson.eitherDecode "{\"firstName\":\"Athan'\" }" :: Either String PartialAppContact
test3 = Data.Aeson.eitherDecode input :: Either String PartialAppContact
更新
根据您的意见,考虑编写实例的思路:
import Data.List (partition)
instance FromJSON PartialAppContact where
parseJSON (Object o) = do
let required = [ "firstName", "lastName", "email", "phoneNumber" ]
let (group1, group2) = partition (\(k,_) -> k `elem` required) (HM.toList o)
reqFields <- forM group1 $ \(k,v) -> do s <- parseJSON v; return (T.unpack k, s)
otherFields <- forM group2 (\(k,v) -> (T.unpack k,) <$> parseJSON v)
return $ PartialAppContact reqFields otherFields
我发现一个有效的实现需要使用 (.:?)
,才能正确实现 可选的已知 字段。从那里,您可以自由分解 HashMap 并重新 parseJSON
它的子字段:
instance FromJSON Foo where
parseJSON (Object o) = do
mfoo <- o .:? "foo"
mbar <- o .:? "bar"
let foundFields = catMaybes [mfoo, mbar]
rest <- mapM (\(k,v) -> (T.unpack k,) <$> parseJSON v)
(toList theRest)
return $ Foo rest -- assuming you're done with `foundFields`
where
theRest = foldr HM.delete o ["foo", "bar"]
要查看评论中讨论的问题的最终实现,see this commit。