|
Packit |
9a2dfb |
-- |
|
|
Packit |
9a2dfb |
-- Module: Data.Aeson
|
|
Packit |
9a2dfb |
-- Copyright: (c) 2011-2016 Bryan O'Sullivan
|
|
Packit |
9a2dfb |
-- (c) 2011 MailRank, Inc.
|
|
Packit |
9a2dfb |
-- License: BSD3
|
|
Packit |
9a2dfb |
-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
|
|
Packit |
9a2dfb |
-- Stability: experimental
|
|
Packit |
9a2dfb |
-- Portability: portable
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Types and functions for working efficiently with JSON data.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- (A note on naming: in Greek mythology, Aeson was the father of Jason.)
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
module Data.Aeson
|
|
Packit |
9a2dfb |
(
|
|
Packit |
9a2dfb |
-- * How to use this library
|
|
Packit |
9a2dfb |
-- $use
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- ** Writing instances by hand
|
|
Packit |
9a2dfb |
-- $manual
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- ** Working with the AST
|
|
Packit |
9a2dfb |
-- $ast
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- ** Decoding to a Haskell value
|
|
Packit |
9a2dfb |
-- $haskell
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- ** Decoding a mixed-type object
|
|
Packit |
9a2dfb |
-- $mixed
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- * Encoding and decoding
|
|
Packit |
9a2dfb |
-- $encoding_and_decoding
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- ** Direct encoding
|
|
Packit |
9a2dfb |
-- $encoding
|
|
Packit |
9a2dfb |
decode
|
|
Packit |
9a2dfb |
, decode'
|
|
Packit |
9a2dfb |
, eitherDecode
|
|
Packit |
9a2dfb |
, eitherDecode'
|
|
Packit |
9a2dfb |
, encode
|
|
Packit |
9a2dfb |
-- ** Variants for strict bytestrings
|
|
Packit |
9a2dfb |
, decodeStrict
|
|
Packit |
9a2dfb |
, decodeStrict'
|
|
Packit |
9a2dfb |
, eitherDecodeStrict
|
|
Packit |
9a2dfb |
, eitherDecodeStrict'
|
|
Packit |
9a2dfb |
-- * Core JSON types
|
|
Packit |
9a2dfb |
, Value(..)
|
|
Packit |
9a2dfb |
, Encoding
|
|
Packit |
9a2dfb |
, fromEncoding
|
|
Packit |
9a2dfb |
, Array
|
|
Packit |
9a2dfb |
, Object
|
|
Packit |
9a2dfb |
-- * Convenience types
|
|
Packit |
9a2dfb |
, DotNetTime(..)
|
|
Packit |
9a2dfb |
-- * Type conversion
|
|
Packit |
9a2dfb |
, FromJSON(..)
|
|
Packit |
9a2dfb |
, Result(..)
|
|
Packit |
9a2dfb |
, fromJSON
|
|
Packit |
9a2dfb |
, ToJSON(..)
|
|
Packit |
9a2dfb |
, KeyValue(..)
|
|
Packit |
9a2dfb |
-- ** Keys for maps
|
|
Packit |
9a2dfb |
, ToJSONKey(..)
|
|
Packit |
9a2dfb |
, ToJSONKeyFunction(..)
|
|
Packit |
9a2dfb |
, FromJSONKey(..)
|
|
Packit |
9a2dfb |
, FromJSONKeyFunction(..)
|
|
Packit |
9a2dfb |
-- ** Liftings to unary and binary type constructors
|
|
Packit |
9a2dfb |
, FromJSON1(..)
|
|
Packit |
9a2dfb |
, parseJSON1
|
|
Packit |
9a2dfb |
, FromJSON2(..)
|
|
Packit |
9a2dfb |
, parseJSON2
|
|
Packit |
9a2dfb |
, ToJSON1(..)
|
|
Packit |
9a2dfb |
, toJSON1
|
|
Packit |
9a2dfb |
, toEncoding1
|
|
Packit |
9a2dfb |
, ToJSON2(..)
|
|
Packit |
9a2dfb |
, toJSON2
|
|
Packit |
9a2dfb |
, toEncoding2
|
|
Packit |
9a2dfb |
-- ** Generic JSON classes and options
|
|
Packit |
9a2dfb |
, GFromJSON(..)
|
|
Packit |
9a2dfb |
, FromArgs(..)
|
|
Packit |
9a2dfb |
, GToJSON
|
|
Packit |
9a2dfb |
, GToEncoding
|
|
Packit |
9a2dfb |
, ToArgs(..)
|
|
Packit |
9a2dfb |
, Zero
|
|
Packit |
9a2dfb |
, One
|
|
Packit |
9a2dfb |
, genericToJSON
|
|
Packit |
9a2dfb |
, genericLiftToJSON
|
|
Packit |
9a2dfb |
, genericToEncoding
|
|
Packit |
9a2dfb |
, genericLiftToEncoding
|
|
Packit |
9a2dfb |
, genericParseJSON
|
|
Packit |
9a2dfb |
, genericLiftParseJSON
|
|
Packit |
9a2dfb |
-- ** Generic and TH encoding configuration
|
|
Packit |
9a2dfb |
, Options
|
|
Packit |
9a2dfb |
, defaultOptions
|
|
Packit |
9a2dfb |
-- *** Options fields
|
|
Packit |
9a2dfb |
-- $optionsFields
|
|
Packit |
9a2dfb |
, fieldLabelModifier
|
|
Packit |
9a2dfb |
, constructorTagModifier
|
|
Packit |
9a2dfb |
, allNullaryToStringTag
|
|
Packit |
9a2dfb |
, omitNothingFields
|
|
Packit |
9a2dfb |
, sumEncoding
|
|
Packit |
9a2dfb |
, unwrapUnaryRecords
|
|
Packit |
9a2dfb |
, tagSingleConstructors
|
|
Packit |
9a2dfb |
-- *** Options utilities
|
|
Packit |
9a2dfb |
, SumEncoding(..)
|
|
Packit |
9a2dfb |
, camelTo2
|
|
Packit |
9a2dfb |
, defaultTaggedObject
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- * Inspecting @'Value's@
|
|
Packit |
9a2dfb |
, withObject
|
|
Packit |
9a2dfb |
, withText
|
|
Packit |
9a2dfb |
, withArray
|
|
Packit |
9a2dfb |
, withNumber
|
|
Packit |
9a2dfb |
, withScientific
|
|
Packit |
9a2dfb |
, withBool
|
|
Packit |
9a2dfb |
, withEmbeddedJSON
|
|
Packit |
9a2dfb |
-- * Constructors and accessors
|
|
Packit |
9a2dfb |
, Series
|
|
Packit |
9a2dfb |
, pairs
|
|
Packit |
9a2dfb |
, foldable
|
|
Packit |
9a2dfb |
, (.:)
|
|
Packit |
9a2dfb |
, (.:?)
|
|
Packit |
9a2dfb |
, (.:!)
|
|
Packit |
9a2dfb |
, (.!=)
|
|
Packit |
9a2dfb |
, object
|
|
Packit |
9a2dfb |
-- * Parsing
|
|
Packit |
9a2dfb |
, json
|
|
Packit |
9a2dfb |
, json'
|
|
Packit |
9a2dfb |
) where
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
import Prelude ()
|
|
Packit |
9a2dfb |
import Prelude.Compat
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
import Data.Aeson.Types.FromJSON (ifromJSON)
|
|
Packit |
9a2dfb |
import Data.Aeson.Encoding (encodingToLazyByteString)
|
|
Packit |
9a2dfb |
import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, eitherDecodeWith, eitherDecodeStrictWith, jsonEOF, json, jsonEOF', json')
|
|
Packit |
9a2dfb |
import Data.Aeson.Types
|
|
Packit |
9a2dfb |
import Data.Aeson.Types.Internal (JSONPath, formatError)
|
|
Packit |
9a2dfb |
import qualified Data.ByteString as B
|
|
Packit |
9a2dfb |
import qualified Data.ByteString.Lazy as L
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Efficiently serialize a JSON value as a lazy 'L.ByteString'.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This is implemented in terms of the 'ToJSON' class's 'toEncoding' method.
|
|
Packit |
9a2dfb |
encode :: (ToJSON a) => a -> L.ByteString
|
|
Packit |
9a2dfb |
encode = encodingToLazyByteString . toEncoding
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
|
|
Packit |
9a2dfb |
-- If this fails due to incomplete or invalid input, 'Nothing' is
|
|
Packit |
9a2dfb |
-- returned.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The input must consist solely of a JSON document, with no trailing
|
|
Packit |
9a2dfb |
-- data except for whitespace.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This function parses immediately, but defers conversion. See
|
|
Packit |
9a2dfb |
-- 'json' for details.
|
|
Packit |
9a2dfb |
decode :: (FromJSON a) => L.ByteString -> Maybe a
|
|
Packit |
9a2dfb |
decode = decodeWith jsonEOF fromJSON
|
|
Packit |
9a2dfb |
{-# INLINE decode #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Efficiently deserialize a JSON value from a strict 'B.ByteString'.
|
|
Packit |
9a2dfb |
-- If this fails due to incomplete or invalid input, 'Nothing' is
|
|
Packit |
9a2dfb |
-- returned.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The input must consist solely of a JSON document, with no trailing
|
|
Packit |
9a2dfb |
-- data except for whitespace.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This function parses immediately, but defers conversion. See
|
|
Packit |
9a2dfb |
-- 'json' for details.
|
|
Packit |
9a2dfb |
decodeStrict :: (FromJSON a) => B.ByteString -> Maybe a
|
|
Packit |
9a2dfb |
decodeStrict = decodeStrictWith jsonEOF fromJSON
|
|
Packit |
9a2dfb |
{-# INLINE decodeStrict #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Efficiently deserialize a JSON value from a lazy 'L.ByteString'.
|
|
Packit |
9a2dfb |
-- If this fails due to incomplete or invalid input, 'Nothing' is
|
|
Packit |
9a2dfb |
-- returned.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The input must consist solely of a JSON document, with no trailing
|
|
Packit |
9a2dfb |
-- data except for whitespace.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This function parses and performs conversion immediately. See
|
|
Packit |
9a2dfb |
-- 'json'' for details.
|
|
Packit |
9a2dfb |
decode' :: (FromJSON a) => L.ByteString -> Maybe a
|
|
Packit |
9a2dfb |
decode' = decodeWith jsonEOF' fromJSON
|
|
Packit |
9a2dfb |
{-# INLINE decode' #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Efficiently deserialize a JSON value from a strict 'B.ByteString'.
|
|
Packit |
9a2dfb |
-- If this fails due to incomplete or invalid input, 'Nothing' is
|
|
Packit |
9a2dfb |
-- returned.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The input must consist solely of a JSON document, with no trailing
|
|
Packit |
9a2dfb |
-- data except for whitespace.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This function parses and performs conversion immediately. See
|
|
Packit |
9a2dfb |
-- 'json'' for details.
|
|
Packit |
9a2dfb |
decodeStrict' :: (FromJSON a) => B.ByteString -> Maybe a
|
|
Packit |
9a2dfb |
decodeStrict' = decodeStrictWith jsonEOF' fromJSON
|
|
Packit |
9a2dfb |
{-# INLINE decodeStrict' #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
eitherFormatError :: Either (JSONPath, String) a -> Either String a
|
|
Packit |
9a2dfb |
eitherFormatError = either (Left . uncurry formatError) Right
|
|
Packit |
9a2dfb |
{-# INLINE eitherFormatError #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Like 'decode' but returns an error message when decoding fails.
|
|
Packit |
9a2dfb |
eitherDecode :: (FromJSON a) => L.ByteString -> Either String a
|
|
Packit |
9a2dfb |
eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
|
|
Packit |
9a2dfb |
{-# INLINE eitherDecode #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Like 'decodeStrict' but returns an error message when decoding fails.
|
|
Packit |
9a2dfb |
eitherDecodeStrict :: (FromJSON a) => B.ByteString -> Either String a
|
|
Packit |
9a2dfb |
eitherDecodeStrict =
|
|
Packit |
9a2dfb |
eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON
|
|
Packit |
9a2dfb |
{-# INLINE eitherDecodeStrict #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Like 'decode'' but returns an error message when decoding fails.
|
|
Packit |
9a2dfb |
eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a
|
|
Packit |
9a2dfb |
eitherDecode' = eitherFormatError . eitherDecodeWith jsonEOF' ifromJSON
|
|
Packit |
9a2dfb |
{-# INLINE eitherDecode' #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- | Like 'decodeStrict'' but returns an error message when decoding fails.
|
|
Packit |
9a2dfb |
eitherDecodeStrict' :: (FromJSON a) => B.ByteString -> Either String a
|
|
Packit |
9a2dfb |
eitherDecodeStrict' =
|
|
Packit |
9a2dfb |
eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON
|
|
Packit |
9a2dfb |
{-# INLINE eitherDecodeStrict' #-}
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $use
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- This section contains basic information on the different ways to
|
|
Packit |
9a2dfb |
-- work with data using this library. These range from simple but
|
|
Packit |
9a2dfb |
-- inflexible, to complex but flexible.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The most common way to use the library is to define a data type,
|
|
Packit |
9a2dfb |
-- corresponding to some JSON data you want to work with, and then
|
|
Packit |
9a2dfb |
-- write either a 'FromJSON' instance, a to 'ToJSON' instance, or both
|
|
Packit |
9a2dfb |
-- for that type.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- For example, given this JSON data:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > { "name": "Joe", "age": 12 }
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- we create a matching data type:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > {-# LANGUAGE DeriveGeneric #-}
|
|
Packit |
9a2dfb |
-- >
|
|
Packit |
9a2dfb |
-- > import GHC.Generics
|
|
Packit |
9a2dfb |
-- >
|
|
Packit |
9a2dfb |
-- > data Person = Person {
|
|
Packit |
9a2dfb |
-- > name :: Text
|
|
Packit |
9a2dfb |
-- > , age :: Int
|
|
Packit |
9a2dfb |
-- > } deriving (Generic, Show)
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The @LANGUAGE@ pragma and 'Generic' instance let us write empty
|
|
Packit |
9a2dfb |
-- 'FromJSON' and 'ToJSON' instances for which the compiler will
|
|
Packit |
9a2dfb |
-- generate sensible default implementations.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
-- instance 'ToJSON' Person where
|
|
Packit |
9a2dfb |
-- \-- No need to provide a 'toJSON' implementation.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- \-- For efficiency, we write a simple 'toEncoding' implementation, as
|
|
Packit |
9a2dfb |
-- \-- the default version uses 'toJSON'.
|
|
Packit |
9a2dfb |
-- 'toEncoding' = 'genericToEncoding' 'defaultOptions'
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- instance 'FromJSON' Person
|
|
Packit |
9a2dfb |
-- \-- No need to provide a 'parseJSON' implementation.
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- We can now encode a value like so:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> encode (Person {name = "Joe", age = 12})
|
|
Packit |
9a2dfb |
-- > "{\"name\":\"Joe\",\"age\":12}"
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $manual
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- When necessary, we can write 'ToJSON' and 'FromJSON' instances by
|
|
Packit |
9a2dfb |
-- hand. This is valuable when the JSON-on-the-wire and Haskell data
|
|
Packit |
9a2dfb |
-- are different or otherwise need some more carefully managed
|
|
Packit |
9a2dfb |
-- translation. Let's revisit our JSON data:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > { "name": "Joe", "age": 12 }
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- We once again create a matching data type, without bothering to add
|
|
Packit |
9a2dfb |
-- a 'Generic' instance this time:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > data Person = Person {
|
|
Packit |
9a2dfb |
-- > name :: Text
|
|
Packit |
9a2dfb |
-- > , age :: Int
|
|
Packit |
9a2dfb |
-- > } deriving Show
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- To decode data, we need to define a 'FromJSON' instance:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > {-# LANGUAGE OverloadedStrings #-}
|
|
Packit |
9a2dfb |
-- >
|
|
Packit |
9a2dfb |
-- > instance FromJSON Person where
|
|
Packit |
9a2dfb |
-- > parseJSON = withObject "Person" $ \v -> Person
|
|
Packit |
9a2dfb |
-- > <$> v .: "name"
|
|
Packit |
9a2dfb |
-- > <*> v .: "age"
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- We can now parse the JSON data like so:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
|
|
Packit |
9a2dfb |
-- > Just (Person {name = "Joe", age = 12})
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- To encode data, we need to define a 'ToJSON' instance. Let's begin
|
|
Packit |
9a2dfb |
-- with an instance written entirely by hand.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
-- instance ToJSON Person where
|
|
Packit |
9a2dfb |
-- \-- this generates a 'Value'
|
|
Packit |
9a2dfb |
-- 'toJSON' (Person name age) =
|
|
Packit |
9a2dfb |
-- 'object' [\"name\" '.=' name, \"age\" '.=' age]
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- \-- this encodes directly to a bytestring Builder
|
|
Packit |
9a2dfb |
-- 'toEncoding' (Person name age) =
|
|
Packit |
9a2dfb |
-- 'pairs' (\"name\" '.=' 'name' '<>' \"age\" '.=' age)
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- We can now encode a value like so:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> encode (Person {name = "Joe", age = 12})
|
|
Packit |
9a2dfb |
-- > "{\"name\":\"Joe\",\"age\":12}"
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- There are predefined 'FromJSON' and 'ToJSON' instances for many
|
|
Packit |
9a2dfb |
-- types. Here's an example using lists and 'Int's:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> decode "[1,2,3]" :: Maybe [Int]
|
|
Packit |
9a2dfb |
-- > Just [1,2,3]
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- And here's an example using the 'Data.Map.Map' type to get a map of
|
|
Packit |
9a2dfb |
-- 'Int's.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)
|
|
Packit |
9a2dfb |
-- > Just (fromList [("bar",2),("foo",1)])
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- While the notes below focus on decoding, you can apply almost the
|
|
Packit |
9a2dfb |
-- same techniques to /encoding/ data. (The main difference is that
|
|
Packit |
9a2dfb |
-- encoding always succeeds, but decoding has to handle the
|
|
Packit |
9a2dfb |
-- possibility of failure, where an input doesn't match our
|
|
Packit |
9a2dfb |
-- expectations.)
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- See the documentation of 'FromJSON' and 'ToJSON' for some examples
|
|
Packit |
9a2dfb |
-- of how you can automatically derive instances in many common
|
|
Packit |
9a2dfb |
-- circumstances.
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $ast
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Sometimes you want to work with JSON data directly, without first
|
|
Packit |
9a2dfb |
-- converting it to a custom data type. This can be useful if you want
|
|
Packit |
9a2dfb |
-- to e.g. convert JSON data to YAML data, without knowing what the
|
|
Packit |
9a2dfb |
-- contents of the original JSON data was. The 'Value' type, which is
|
|
Packit |
9a2dfb |
-- an instance of 'FromJSON', is used to represent an arbitrary JSON
|
|
Packit |
9a2dfb |
-- AST (abstract syntax tree). Example usage:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> decode "{\"foo\": 123}" :: Maybe Value
|
|
Packit |
9a2dfb |
-- > Just (Object (fromList [("foo",Number 123)]))
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > >>> decode "{\"foo\": [\"abc\",\"def\"]}" :: Maybe Value
|
|
Packit |
9a2dfb |
-- > Just (Object (fromList [("foo",Array (fromList [String "abc",String "def"]))]))
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Once you have a 'Value' you can write functions to traverse it and
|
|
Packit |
9a2dfb |
-- make arbitrary transformations.
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $haskell
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- We can decode to any instance of 'FromJSON':
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > λ> decode "[1,2,3]" :: Maybe [Int]
|
|
Packit |
9a2dfb |
-- > Just [1,2,3]
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Alternatively, there are instances for standard data types, so you
|
|
Packit |
9a2dfb |
-- can use them directly. For example, use the 'Data.Map.Map' type to
|
|
Packit |
9a2dfb |
-- get a map of 'Int's.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > λ> import Data.Map
|
|
Packit |
9a2dfb |
-- > λ> decode "{\"foo\":1,\"bar\":2}" :: Maybe (Map String Int)
|
|
Packit |
9a2dfb |
-- > Just (fromList [("bar",2),("foo",1)])
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $mixed
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The above approach with maps of course will not work for mixed-type
|
|
Packit |
9a2dfb |
-- objects that don't follow a strict schema, but there are a couple
|
|
Packit |
9a2dfb |
-- of approaches available for these.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The 'Object' type contains JSON objects:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > λ> decode "{\"name\":\"Dave\",\"age\":2}" :: Maybe Object
|
|
Packit |
9a2dfb |
-- > Just (fromList [("name",String "Dave"),("age",Number 2)])
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- You can extract values from it with a parser using 'parse',
|
|
Packit |
9a2dfb |
-- 'parseEither' or, in this example, 'parseMaybe':
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > λ> do result <- decode "{\"name\":\"Dave\",\"age\":2}"
|
|
Packit |
9a2dfb |
-- > flip parseMaybe result $ \obj -> do
|
|
Packit |
9a2dfb |
-- > age <- obj .: "age"
|
|
Packit |
9a2dfb |
-- > name <- obj .: "name"
|
|
Packit |
9a2dfb |
-- > return (name ++ ": " ++ show (age*2))
|
|
Packit |
9a2dfb |
-- >
|
|
Packit |
9a2dfb |
-- > Just "Dave: 4"
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Considering that any type that implements 'FromJSON' can be used
|
|
Packit |
9a2dfb |
-- here, this is quite a powerful way to parse JSON. See the
|
|
Packit |
9a2dfb |
-- documentation in 'FromJSON' for how to implement this class for
|
|
Packit |
9a2dfb |
-- your own data types.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- The downside is that you have to write the parser yourself; the
|
|
Packit |
9a2dfb |
-- upside is that you have complete control over the way the JSON is
|
|
Packit |
9a2dfb |
-- parsed.
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $encoding_and_decoding
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Decoding is a two-step process.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- * When decoding a value, the process is reversed: the bytes are
|
|
Packit |
9a2dfb |
-- converted to a 'Value', then the 'FromJSON' class is used to
|
|
Packit |
9a2dfb |
-- convert to the desired type.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- There are two ways to encode a value.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- * Convert to a 'Value' using 'toJSON', then possibly further
|
|
Packit |
9a2dfb |
-- encode. This was the only method available in aeson 0.9 and
|
|
Packit |
9a2dfb |
-- earlier.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- * Directly encode (to what will become a 'L.ByteString') using
|
|
Packit |
9a2dfb |
-- 'toEncoding'. This is much more efficient (about 3x faster, and
|
|
Packit |
9a2dfb |
-- less memory intensive besides), but is only available in aeson
|
|
Packit |
9a2dfb |
-- 0.10 and newer.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- For convenience, the 'encode' and 'decode' functions combine both
|
|
Packit |
9a2dfb |
-- steps.
|
|
Packit |
9a2dfb |
|
|
Packit |
9a2dfb |
-- $encoding
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- In older versions of this library, encoding a Haskell value
|
|
Packit |
9a2dfb |
-- involved converting to an intermediate 'Value', then encoding that.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- A \"direct\" encoder converts straight from a source Haskell value
|
|
Packit |
9a2dfb |
-- to a 'BL.ByteString' without constructing an intermediate 'Value'.
|
|
Packit |
9a2dfb |
-- This approach is faster than 'toJSON', and allocates less memory.
|
|
Packit |
9a2dfb |
-- The 'toEncoding' method makes it possible to implement direct
|
|
Packit |
9a2dfb |
-- encoding with low memory overhead.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- To complicate matters, the default implementation of 'toEncoding'
|
|
Packit |
9a2dfb |
-- uses 'toJSON'. Why? The 'toEncoding' method was added to this
|
|
Packit |
9a2dfb |
-- library much more recently than 'toJSON'. Using 'toJSON' ensures
|
|
Packit |
9a2dfb |
-- that packages written against older versions of this library will
|
|
Packit |
9a2dfb |
-- compile and produce correct output, but they will not see any
|
|
Packit |
9a2dfb |
-- speedup from direct encoding.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- To write a minimal implementation of direct encoding, your type
|
|
Packit |
9a2dfb |
-- must implement GHC's 'Generic' class, and your code should look
|
|
Packit |
9a2dfb |
-- like this:
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
-- 'toEncoding' = 'genericToEncoding' 'defaultOptions'
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- What if you have more elaborate encoding needs? For example,
|
|
Packit |
9a2dfb |
-- perhaps you need to change the names of object keys, omit parts of
|
|
Packit |
9a2dfb |
-- a value.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- To encode to a JSON \"object\", use the 'pairs' function.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
-- 'toEncoding' (Person name age) =
|
|
Packit |
9a2dfb |
-- 'pairs' (\"name\" '.=' 'name' '<>' \"age\" '.=' age)
|
|
Packit |
9a2dfb |
-- @
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- Any container type that implements 'Foldable' can be encoded to a
|
|
Packit |
9a2dfb |
-- JSON \"array\" using 'foldable'.
|
|
Packit |
9a2dfb |
--
|
|
Packit |
9a2dfb |
-- > > import Data.Sequence as Seq
|
|
Packit |
9a2dfb |
-- > > encode (Seq.fromList [1,2,3])
|
|
Packit |
9a2dfb |
-- > "[1,2,3]"
|