Fault tolerant JSON parsing

2020-06-12 02:33发布

问题:

I'm using Data.Aeson to parse some JSON into a Record type. From time to time data is added to the JSON and this breaks my code as Aeson complains something to the effect of:

expected Object with 21 name/value pairs but got 23 name/value

I'd really prefer to parse the JSON in a fault tolerant way -- I don't care if more fields are added to the JSON at a later date, just parse whatever you can! Is there a way to achieve this fault tolerance? Here's my code:

myRecordFromJSONString :: BS.ByteString -> Maybe MyRecord
myRecordFromJSONString s = case Data.Attoparsec.parse json s of
  Done _rest res -> Data.Aeson.Types.parseMaybe parseJSON res
  _              -> Nothing

I should add that I'm using deriveJSON from Data.Aeson.TH to generate the parsing code. If I write the FromJSON code manually it's fault tolerant but I'd like to not have to do that...

回答1:

If you are using GHC 7.2 or 7.4, the new generics support in aeson doesn't check for extra fields. I'm not sure if this is by design or not but we use it for the same reason.

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

import Data.Aeson
import qualified Data.Aeson.Types
import Data.Attoparsec
import qualified Data.ByteString as BS
import Data.ByteString.Char8 ()
import GHC.Generics

data MyRecord = MyRecord
  { field1 :: Int
  } deriving (Generic, Show)

instance FromJSON MyRecord

myRecordFromJSONString :: BS.ByteString -> Maybe MyRecord
myRecordFromJSONString s = case Data.Attoparsec.parse json s of
  Done _rest res -> Data.Aeson.Types.parseMaybe parseJSON res
  _              -> Nothing

main :: IO ()
main = do
  let parsed = myRecordFromJSONString "{ \"field1\": 1, \"field2\": 2 }"
  print parsed

Running this would fail with the TH derived instance due to 'field2' not existing in the record. The Generic instance returns the desired result:

Just (MyRecord {field1 = 1})