2016-04-06 3 views
0

Итак, я ударил что-то вроде блокпоста с разбором следующего JSON с библиотекой Haskell Aeson.Разбор вложенных гетерогенных массивов JSON с помощью Aeson

Так что у меня есть следующие:

"packetX_Name": [ 
    "container", 
    [ 
    { 
     "field1": "value1", 
     "field2": "value2" 
    }, 
    { 
     "field1": "value3", 
     "field2": "value4" 
    }, 
    { 
     "field1": "value5", 
     "field2": "value6" 
    } 
    ] 
], 
"packetY_Name": [ 
    "container", 
    [ 
    { 
     "field1": "value7", 
     "field2": "value8" 
    }, 
    { 
     "field1": "value9", 
     "field2": "value10" 
    } 
    ] 
], 
etc... 

А я бы в идеале хотел бы разобрать это, используя типы данных, как это:

data ExtractedPacket = ExtractedPacket 
    { packetName :: String 
    , packetFields :: [ExtractedPacketField] 
    } deriving (Show,Eq) 

instance FromJSON ExtractedPacket where 
    parseJSON = blah 

data ExtractedPacketField = ExtractedPacketField 
    { field1 :: String 
    , field2 :: String 
    } deriving (Show,Eq) 

instance FromJSON ExtractedPacketField where 
    parseJSON = blah 

И получить что-то вроде следующего:

ExtractedPacket 
    "packetX_Name" 
    [ ExtractedPacketField "value1" "value2" 
    , ExtractedPacketField "value3" "value4" 
    , ExtractedPacketField "value5" "value6" 
    ] 

ExtractedPacket 
"packetY_Name" 
    [ ExtractedPacketField "value7" "value8" 
    , ExtractedPacketField "value10" "value10" 
    ] 

В этом примере JSON описываются сетевые пакеты, и каждый пакет имеет другое имя (например, «пакет X» _Name "), который не может быть проанализирован так же, как и поле« 1 »или« поле2 ». Все будет по-разному. Большинство примеров Aeson там совершенно бесполезны, когда дело доходит до таких ситуаций. Я заметил эту функцию в документации API под названием withArray, что соответствует на String, но я на проигрыш, как к тому, что использовать для (Array -> Parser a)

Часть, я действительно застрял на разборе является гетерогенный массив, начинается с String «container», а затем имеет массив со всеми объектами в нем. До сих пор я индексировал прямо к массиву объектов, но система типов начала становиться настоящим лабиринтом, и мне было очень трудно подойти к этому способом, который не является уродливым и хакерским. Кроме того, Aeson не создает очень полезные сообщения об ошибках.

Любые идеи о том, как подойти к этому?

ответ

1

В более сложных примерах, как эти, это хорошо иметь в виду, что под типа эсон Value простые структуры данных - Vector для массивов и HashMap объектов. Немного более экзотично, чем списки и карты, к которым мы привыкли иметь дело, но все же структуры данных, которые имеют Foldable и Traversable экземпляров. Имея это в виду, мы можем объявить эти экземпляры:

{-# LANGUAGE OverloadedStrings #-} 

import qualified Control.Lens as Lens 
import qualified Data.Foldable as Foldable 
import qualified Data.Text.Strict.Lens as Lens 
import   Data.Aeson 
import   Data.Aeson.Types 

newtype ExtractedPackets = 
    ExtractedPackets [ExtractedPacket] deriving (Show) 

instance FromJSON ExtractedPackets where 
    parseJSON (Object o) = do 
    let subparsers = 
      [ ExtractedPacket (Lens.view Lens.unpacked key) <$> parseJSON packets 
      | (key, Array values) <- Lens.itoList o 
      , [email protected](Array _) <- Foldable.toList values] 
    packets <- sequence subparsers 
    return (ExtractedPackets packets) 
    parseJSON invalid = 
    typeMismatch "ExtractedPackets" invalid 

instance FromJSON ExtractedPacketField where 
    parseJSON (Object o) = 
    ExtractedPacketField <$> o .: "field1" <*> o .: "field2" 
    parseJSON invalid = 
    typeMismatch "ExtractedPacketField" invalid 

Мы должны NewType список пакетов, потому что есть уже FromJSON экземпляр для FromJSON a => FromJSON [a] и не делать то, что мы хотим (это, в частности, только оборудованный для работы с однородными списками).

Как только мы это сделаем, мы можем получить доступ к хэшмапу внутри объекта и повернуть его ключи и значения в виде кортежей. Сопоставляя над кортежами, мы создаем [Parser ExpectedPacket], который мы можем sequence в Parser [ExpectedPacket]. Я использую lens здесь, чтобы делать скучные вещи, например, конвертировать между упакованными и распакованными строками или разбивать хэш-карту на кортежи с ключом и ценностью. Вы можете использовать пакеты text и unordered-containers для достижения тех же целей, если вы не хотите тянуть lens.

Это, кажется, работает на приведенном примере:

λ> eitherDecode bytes :: Either String ExtractedPackets 
Right (ExtractedPackets [ExtractedPacket {packetName = "packetX_Name", 
packetFields = [ExtractedPacketField {field1 = "value1", field2 = 
"value2"},ExtractedPacketField {field1 = "value3", field2 = 
"value4"},ExtractedPacketField {field1 = "value5", field2 = 
"value6"}]},ExtractedPacket {packetName = "packetY_Name", packetFields 
= [ExtractedPacketField {field1 = "value7", field2 = 
"value8"},ExtractedPacketField {field1 = "value9", field2 = 
"value10"}]}]) 

Наконец, я часто нахожу, что использование typeMismatch и eitherDecode быть чрезвычайно полезно при отладке экземпляров эсона.

+0

Да, это работает и для меня. Спасибо! Это одно из более прохладных способов использования списков, которые я видел до сих пор.Раньше я играл с Vectors и HashMaps, но типы были бы выведены в Value так, как мне трудно было отлаживать. – carpemb

Смежные вопросы