2015-11-22 6 views
0

по какой-то причине я не могу оборачивать голову вокруг произвольных успешных партитур в Эзоне, не создавая обрывки всей системы и вызывая утечку пространства.Разбор «остальной» объекта эзона

Вот мой вопрос:

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, вне парсера (из-за ленивого байта, который подается в него), но я явно не уверен в этом. Итак, я попробовал другой подход:

instance FromJSON Foo where 
    parseJSON (Object o) = 
    (Foo . filter (\(k,_) -> k `elem` toIgnore)) <$> 
     parseJSON (Object o) 
    where 
     toIgnore = ["foo", "bar"] 
    parseJSON _ = empty 

Но это также кажется причиной тупиковой/пространство утечки (не уверен точно, что диагностировать эту остановку исполнения). Каким будет рекомендованный способ принять все кроме несколько ключей объекта? Мне нужно сопоставить шаблон по структуре (Object o), потому что я вручную просматриваю o .: "foo" и o .: "bar" в другом компоненте для моего типа данных. В идеале я хотел бы просто удалить эти ключи из содержания и продолжить синтаксический анализ, потому что я уже учёл их (отсюда - «остальное»).

Есть ли надежда?

+1

Каков ваш тестовый код, который демонстрирует утечку пространства? – ErikR

+0

Это может быть много для компиляции, но это из [этого репозитория] (https://github.com/athanclark/contact-logger/blob/4250c8f36452934b96044918e5f5321237f45883/src/Application/Api.hs#L215) - если вы ': установите -XOverloadedStrings' и 'import Application.Api' в ghci и запустите [эту попытку разбора] (http://lpaste.net/145756), ваш компьютер раздувается. __Edit__: Предполагается, что вы используете GHC> = 7.10 –

+0

Другое редактирование: [более точная попытка проанализировать] (http://lpaste.net/145756) –

ответ

1

Для вашего 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": "[email protected]" 
| , "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 
+0

На самом деле это неверная реализация - хотя использование имени переменной вводило в заблуждение, все поля '' firstName ''и т. Д. Являются _optional_. Я сделал рабочее решение [здесь (наконец)] (https://github.com/athanclark/contact-logger/blob/e8f293000f64cfc8964a88367dd162695dbbb163/src/Application/Api.hs#L208) - Мне нужно было использовать '(.: ?) ', чтобы получить необязательные поля _known_, затем вручную разложите структуру« rest »и переподготовьте подполя' Maybe String' с помощью 'mapM'. –

0

Я нашел, что для выполнения работы требуется использование (.:?), чтобы исправить y реализация опционально, известно полей. Оттуда, вы можете свободно разложить 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.