HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Encoding and decoding JSON

2 declarations

Instantiating Aeson.ToJSON

4 declarations

JSON Objects

Instantiating Aeson.FromJSON

2 declarations

JSON Objects

Codec

4 declarations
typetype JSONCodec a = ValueCodec a a
#

A completed autodocodec for parsing and rendering a Value.

You can use a value of this type to get everything else for free:

  • Encode values to JSON using toJSONViaCodec or toJSONVia

  • Decode values from JSON using parseJSONViaCodec or parseJSONVia

  • Produce a JSON Schema using jsonSchemaViaCodec or jsonSchemaVia from autodocodec-schema

  • Encode to and decode from Yaml using autodocodec-yaml

  • Produce a human-readible YAML schema using renderColouredSchemaViaCodec from autodocodec-yaml

  • Produce a Swagger2 schema using autodocodec-swagger2

  • Produce a OpenAPI3 schema using autodocodec-openapi3

classclass HasCodec value where
#

A class for values which have a canonical codec.

There are no formal laws for this class. If you really want a law, it should be "Whomever uses the codec from your instance should not be surprised."

Methods

  • codec :: JSONCodec value

    A codec for a single value

    See the sections on helper functions for implementing this for plenty of examples.

  • listCodecForStringCompatibility :: JSONCodec [value]

    A codec for a list of values

    This is really only useful for cases like Char and String. We didn't call it listCodec so we could use that name for making a codec for a list of values from a single codec instead.

Instances45HasCodec, …
classclass HasObjectCodec object where
#

A class for values which have a canonical object codec.

There are no formal laws for this class. If you really want a law, it should be "Whomever uses the codec from your instance should not be surprised."

Methods

  • objectCodec :: JSONObjectCodec object

    A object codec for the value

    See the sections on helper functions for implementing this for plenty of examples.

Writing a codec

3 declarations
valueobject :: Text -> ObjectCodec input output -> ValueCodec input output
#

An object codec with a given name

Example usage
data Example = Example
  { exampleText :: !Text,
    exampleBool :: !Bool
  }

instance HasCodec Example where
  codec =
    object "Example" $
      Example
        <$> requiredField "text" "a text" .= exampleText
        <*> requiredField "bool" "a bool" .= exampleBool
API Note

This is a forward-compatible version ObjectOfCodec with a name.

object name = ObjectOfCodec (Just name)
valuenamed :: Text -> ValueCodec input output -> ValueCodec input output
#

Name a codec.

This is used to allow for references to the codec, and that's necessary to produce finite documentation for recursive codecs.

API Note

This is a forward-compatible version of ReferenceCodec.

named = ReferenceCodec
valuecodecViaAeson
  1. :: (FromJSON a, ToJSON a)
  2. => Text

    Name

  3. -> JSONCodec a
#

Produce a codec using a type's FromJSON and ToJSON instances.

You will only want to use this if you cannot figure out how to produce a JSONCodec for your type.

Note that this will not have good documentation because, at a codec level, it's just parsing and rendering a Value.

Example usage
Example2 expressions
toJSONVia (codecViaAeson "Int") (5 :: Int)Number 5.0JSON.parseMaybe (parseJSONVia (codecViaAeson "Int")) (Number 5) :: Maybe IntJust 5

Field codecs

With documentation

value(.=)
  1. :: ObjectCodec oldInput output
  2. -> newInput -> oldInput
  3. -> ObjectCodec newInput output
#

Infix version of lmapCodec

Use this function to supply the rendering side of a codec.

(.=) = flip lmapCodec
Example usage
data Example = Example
  { exampleText :: !Text,
    exampleBool :: !Bool
  }
instance HasCodec Example where
  codec =
    object "Example" $
      Example
        <$> requiredField "text" .= exampleText
        <*> requiredField "bool" .= exampleBool
valueoptionalFieldOrNull
  1. :: HasCodec output
  2. => Text

    Key

  3. -> Text

    Documentation

  4. -> ObjectCodec (Maybe output) (Maybe output)
#

An optional, or null, field

During decoding, the field may be in the object. Nothing will be parsed if it is not. If the field is null, then it will be parsed as Nothing as well.

During encoding, the field will be in the object if it is not Nothing, and omitted otherwise.

valueoptionalFieldWithDefault
  1. :: HasCodec output
  2. => Text

    Key

  3. -> output

    Default value

  4. -> Text

    Documentation

  5. -> ObjectCodec output output
#

An optional field with a default value

During decoding, the field may be in the object. The default value will be parsed otherwise.

During encoding, the field will be in the object. The default value is ignored.

The shown version of the default value will appear in the documentation.

valuerequiredFieldWith
  1. :: Text

    Key

  2. -> ValueCodec input output

    Codec for the value

  3. -> Text

    Documentation

  4. -> ObjectCodec input output
#

A required field

During decoding, the field must be in the object.

During encoding, the field will always be in the object.

valueoptionalFieldWithDefaultWith
  1. :: Text

    Key

  2. -> JSONCodec output

    Codec for the value

  3. -> output

    Default value

  4. -> Text

    Documentation

  5. -> ObjectCodec output output
#

An optional field with default value

During decoding, the field may be in the object. The default value will be parsed otherwise.

During encoding, the field will always be in the object. The default value is ignored.

The shown version of the default value will appear in the documentation.

valueoptionalFieldWithOmittedDefaultWith
  1. :: Eq output
  2. => Text

    Key

  3. -> JSONCodec output

    Codec for the value

  4. -> output

    Default value

  5. -> Text

    Documentation

  6. -> ObjectCodec output output
#

An optional field with default value that can be omitted when encoding

During decoding, the field may be in the object. The default value will be parsed otherwise.

During encoding, the field will be omitted from the object if it is equal to the default value.

The shown version of the default value will appear in the documentation.

Documentation-less versions of field codecs

Writing your own value codecs.

Primitive codecs

valuenullCodec :: JSONCodec ()
#

Codec for null

Example usage
Example3 expressions
toJSONVia nullCodec ()NullJSON.parseMaybe (parseJSONVia nullCodec) NullJust ()JSON.parseMaybe (parseJSONVia nullCodec) (Number 5)Nothing
API Note

This is a forward-compatible version of NullCodec.

nullCodec = NullCodec

Codec for boolean values

Example usage
Example1 expression
toJSONVia boolCodec TrueBool True
API Note

This is a forward-compatible version of BoolCodec without a name.

boolCodec = BoolCodec Nothing

Codec for text values

Example usage
Example1 expression
toJSONVia textCodec "hello"String "hello"
API Note

This is a forward-compatible version of StringCodec without a name.

textCodec = StringCodec Nothing

Codec for String values

Example usage
Example1 expression
toJSONVia stringCodec "hello"String "hello"
WARNING

This codec uses unpack and pack to dimap a textCodec, so it does not roundtrip.

Example1 expression
toJSONVia stringCodec "\55296"String "\65533"
API Note

This is a String version of textCodec.

Codec for Integer values

This codec does a bounds check for the range [-10^1024, 10^1024] it can safely parse very large numbers.

Example usage
Example4 expressions
toJSONVia integerCodec 5Number 5.0toJSONVia integerCodec (-1000000000000)Number (-1.0e12)JSON.parseMaybe (parseJSONVia integerCodec) (Number (-4.0))Just (-4)JSON.parseMaybe (parseJSONVia integerCodec) (Number (scientific 1 100000000))Nothing
API Note

This is a forward-compatible version of IntegerCodec without a name.

scientificCodec = IntegerCodec Nothing Nothing

For a codec without this protection, see unsafeUnboundedIntegerCodec.

Codec for Integer values with bounds

Example usage
Example5 expressions
let c = integerWithBoundsCodec Bounds {boundsLower = Just 2, boundsUpper = Just 4}toJSONVia c 3Number 3.0toJSONVia c 5Number 5.0JSON.parseMaybe (parseJSONVia c) (Number 3)Just 3JSON.parseMaybe (parseJSONVia c) (Number 5)Nothing
API Note

This is a forward-compatible version of IntegerCodec without a name.

integerWithBoundsCodec bounds = IntegerCodec Nothing bounds

Codec for Scientific values

Example usage
Example2 expressions
toJSONVia scientificCodec 5Number 5.0JSON.parseMaybe (parseJSONVia scientificCodec) (Number 3)Just 3.0
WARNING

Scientific is a type that is only for JSON parsing and rendering. Do not use it for any calculations. Instead, convert to another number type before doing any calculations.

λ> (1 / 3) :: Scientific
*** Exception: fromRational has been applied to a repeating decimal which can't be represented as a Scientific! It's better to avoid performing fractional operations on Scientifics and convert them to other fractional types like Double as early as possible.
API Note

This is a forward-compatible version of NumberCodec without a name.

scientificCodec = NumberCodec Nothing Nothing

Codec for Scientific values with bounds

Example usage
Example5 expressions
let c = scientificWithBoundsCodec Bounds {boundsLower = Just 2, boundsUpper = Just 4}toJSONVia c 3Number 3.0toJSONVia c 5Number 5.0JSON.parseMaybe (parseJSONVia c) (Number 3)Just 3.0JSON.parseMaybe (parseJSONVia c) (Number 5)Nothing
WARNING

Scientific is a type that is only for JSON parsing and rendering. Do not use it for any calculations. Instead, convert to another number type before doing any calculations.

λ> (1 / 3) :: Scientific
*** Exception: fromRational has been applied to a repeating decimal which can't be represented as a Scientific! It's better to avoid performing fractional operations on Scientifics and convert them to other fractional types like Double as early as possible.
API Note

This is a forward-compatible version of NumberCodec without a name.

scientificWithBoundsCodec bounds = NumberCodec Nothing bounds

Codec for a Value

This is essentially your escape-hatch for when you would normally need a monad instance for Codec. You can build monad parsing by using valueCodec together with bimapCodec and supplying your own parsing function.

Note that this _does_ mean that the documentation will just say that you are parsing and rendering a value, so you may want to document the extra parsing further using <?>.

API Note

This is a forward-compatible version of ValueCodec.

valueCodec = ValueCodec

Bounds

datadata Bounds a
#

Constructors

Instances7Functor, Eq, Ord, Show, Generic, Validity, …

Integral codecs

A codec for bounded integers like Int, Int8, and Word.

This codec will not have a name, and it will use the boundedNumberBounds to add number bounds.

Example4 expressions
let c = boundedIntegralCodec :: JSONCodec Int8toJSONVia c 5Number 5.0JSON.parseMaybe (parseJSONVia c) (Number 100)Just 100JSON.parseMaybe (parseJSONVia c) (Number 200)Nothing

A codec for Natural values.

This codec does a bounds check for the range [0, 10^1024] it can safely parse very large numbers.

For a codec without this protection, see unsafeUnboundedNaturalCodec.

Example usage
Example4 expressions
toJSONVia naturalCodec 5Number 5.0toJSONVia naturalCodec (1000000000000)Number 1.0e12JSON.parseMaybe (parseJSONVia naturalCodec) (Number 4.0)Just 4JSON.parseMaybe (parseJSONVia naturalCodec) (Number (scientific 1 100000000))Nothing

Literal value codecs

A codec for a literal piece of Text.

During parsing, only the given Text is accepted.

During rendering, the given Text is always output.

Example usage
Example5 expressions
let c = literalTextCodec "hello"toJSONVia c "hello"String "hello"toJSONVia c "world"String "hello"JSON.parseMaybe (parseJSONVia c) (String "hello")Just "hello"JSON.parseMaybe (parseJSONVia c) (String "world")Nothing
valueliteralTextValueCodec :: value -> Text -> JSONCodec value
#

A codec for a literal value corresponding to a literal piece of Text.

During parsing, only the given Text is accepted.

During rendering, the given value is always output.

Example usage
Example5 expressions
let c = literalTextValueCodec True "yes"toJSONVia c TrueString "yes"toJSONVia c FalseString "yes"JSON.parseMaybe (parseJSONVia c) (String "yes") :: Maybe BoolJust TrueJSON.parseMaybe (parseJSONVia c) (String "no") :: Maybe BoolNothing

Enums

valueshownBoundedEnumCodec :: (Show enum, Eq enum, Enum enum, Bounded enum) => JSONCodec enum
#

A codec for a Bounded Enum that uses its Show instance to have the values correspond to literal Text values.

Example usage
Example4 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Enum, Bounded)let c = shownBoundedEnumCodectoJSONVia c AppleString "Apple"JSON.parseMaybe (parseJSONVia c) (String "Orange") :: Maybe FruitJust Orange
valueboundedEnumCodec
  1. :: (Eq enum, Enum enum, Bounded enum)
  2. => enum -> Text
  3. -> JSONCodec enum
#

A codec for a Bounded Enum that uses the provided function to have the values correspond to literal Text values.

Example usage
Example2 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Enum, Bounded):{  let c = boundedEnumCodec $ \case        Apple -> "foo"        Orange -> "bar":}
Example2 expressions
toJSONVia c AppleString "foo"JSON.parseMaybe (parseJSONVia c) (String "bar") :: Maybe FruitJust Orange
valuestringConstCodec
  1. :: Eq constant
  2. => NonEmpty (constant, Text)
  3. -> JSONCodec constant
#

A codec for an enum that can be written as constant string values

Example usage
Example4 expressions
data Fruit = Apple | Orange deriving (Show, Eq)let c = stringConstCodec [(Apple, "foo"), (Orange, "bar")]toJSONVia c OrangeString "bar"JSON.parseMaybe (parseJSONVia c) (String "foo") :: Maybe FruitJust Apple
WARNING

If you don't provide a string for one of the type's constructors, the last string in the list will be used instead:

Example2 expressions
let c = stringConstCodec [(Apple, "foo")]toJSONVia c OrangeString "foo"
valueenumCodec
  1. :: Eq enum
  2. => NonEmpty (enum, Codec context enum enum)
  3. -> Codec context enum enum
#

A codec for an enum that can be written each with their own codec.

WARNING

If you don't provide a string for one of the type's constructors, the last codec in the list will be used instead.

Sum type codecs

valueeitherCodec
  1. :: Codec context input1 output1
  2. -> Codec context input2 output2
  3. -> Codec context (Either input1 input2) (Either output1 output2)
#

Either codec

During encoding, parse a value according to either codec. During encoding, use the corresponding codec to encode either value.

HasCodec instance for sum types

To write a HasCodec instance for sum types, you will need to decide whether encoding is disjoint or not. The default, so also the implementation of this function, is possiblyJointEitherCodec, but you may want to use disjointEitherCodec instead.

Ask yourself: Can the encoding of a Left value be decoded as Right value (or vice versa)?

Yes -> use possiblyJointEitherCodec.

No -> use disjointEitherCodec.

Example usage
Example4 expressions
let c = eitherCodec codec codec :: JSONCodec (Either Int String)toJSONVia c (Left 5)Number 5.0toJSONVia c (Right "hello")String "hello"JSON.parseMaybe (parseJSONVia c) (String "world") :: Maybe (Either Int String)Just (Right "world")
API Note

This is a forward-compatible version of possiblyJointEitherCodec.

eitherCodec = possiblyJointEitherCodec
valuedisjointEitherCodec
  1. :: Codec context input1 output1
  2. -> Codec context input2 output2
  3. -> Codec context (Either input1 input2) (Either output1 output2)
#

Possibly joint either codec

During encoding, parse a value according to either codec. During encoding, use the corresponding codec to encode either value.

This codec is for the case in which parsing must be disjoint.

HasCodec instance for sum types with an encoding that is definitely disjoint.

The eitherCodec can be used to implement HasCodec instances for sum types for which the encoding is definitely disjoint.

Example2 expressions
data War = WorldWar Word8 | OtherWar Text deriving (Show, Eq):{  instance HasCodec War where   codec =     dimapCodec f g $       disjointEitherCodec         (codec :: JSONCodec Word8)         (codec :: JSONCodec Text)     where       f = \case         Left w -> WorldWar w         Right t -> OtherWar t       g = \case         WorldWar w -> Left w         OtherWar t -> Right t:}

Note that this incoding is indeed disjoint because an encoded String can never be parsed as an Word8 and vice versa.

Example3 expressions
toJSONViaCodec (WorldWar 2)Number 2.0toJSONViaCodec (OtherWar "OnDrugs")String "OnDrugs"JSON.parseMaybe parseJSONViaCodec (String "of the roses") :: Maybe WarJust (OtherWar "of the roses")
WARNING

If it turns out that the encoding of a value is not disjoint, decoding may fail and documentation may be wrong.

Example2 expressions
let c = disjointEitherCodec (codec :: JSONCodec Int) (codec :: JSONCodec Int)JSON.parseMaybe (parseJSONVia c) (Number 5) :: Maybe (Either Int Int)Nothing

Encoding still works as expected, however:

Example2 expressions
toJSONVia c (Left 5)Number 5.0toJSONVia c (Right 6)Number 6.0
Example usage
Example2 expressions
toJSONVia (disjointEitherCodec (codec :: JSONCodec Int) (codec :: JSONCodec String)) (Left 5)Number 5.0toJSONVia (disjointEitherCodec (codec :: JSONCodec Int) (codec :: JSONCodec String)) (Right "hello")String "hello"
API Note

This is a forward-compatible version of 'EitherCodec DisjointUnion'.

disjointEitherCodec = EitherCodec DisjointUnion
valuepossiblyJointEitherCodec
  1. :: Codec context input1 output1
  2. -> Codec context input2 output2
  3. -> Codec context (Either input1 input2) (Either output1 output2)
#

Possibly joint either codec

During encoding, parse a value according to either codec. During encoding, use the corresponding codec to encode either value.

This codec is for the case in which parsing may not be disjoint.

HasCodec instance for sum types with an encoding that is not disjoint.

The eitherCodec can be used to implement HasCodec instances for sum types. If you just have two codecs that you want to try in order, while parsing, you can do this:

Example1 expression
:{  data Ainur    = Valar Text Text    | Maiar Text    deriving (Show, Eq):}
Example1 expression
:{  instance HasCodec Ainur where    codec =      dimapCodec f g $        possiblyJointEitherCodec          (object "Valar" $            (,)             <$> requiredField "domain" "Domain which the Valar rules over" .= fst             <*> requiredField "name" "Name of the Valar" .= snd)          (object "Maiar" $ requiredField "name" "Name of the Maiar")      where        f = \case          Left (domain, name) -> Valar domain name          Right name -> Maiar name        g = \case          Valar domain name -> Left (domain, name)          Maiar name -> Right name:}

Note that this encoding is indeed not disjoint, because a Valar object can parse as a Maiar value.

Example3 expressions
toJSONViaCodec (Valar "Stars" "Varda")Object (fromList [("domain",String "Stars"),("name",String "Varda")])toJSONViaCodec (Maiar "Sauron")Object (fromList [("name",String "Sauron")])JSON.parseMaybe parseJSONViaCodec (Object (Compat.fromList [("name",String "Olorin")])) :: Maybe AinurJust (Maiar "Olorin")
WARNING

The order of the codecs in a possiblyJointEitherCodec matters.

In the above example, decoding works as expected because the Valar case is parsed first. If the Maiar case were first in the possiblyJointEitherCodec, then Valar could never be parsed.

API Note

This is a forward-compatible version of 'EitherCodec PossiblyJointUnion'.

possiblyJointEitherCodec = EitherCodec PossiblyJointUnion

Discriminated unions

valuemapToEncoder :: b -> Codec context b any -> Codec context a ()
#

Wrap up a value of type b with its codec to produce and encoder for as that ignores its input and instead encodes the value b. This is useful for building discriminatedUnionCodecs.

valuediscriminatedUnionCodec
  1. :: Text

    propertyName

  2. -> (input -> (Discriminator, ObjectCodec input ()))

    how to encode the input

    Use mapToEncoder to produce the ObjectCodecs.

  3. -> HashMap Discriminator (Text, ObjectCodec Void output)

    how to decode the output

    The Text field is the name to use for the object schema.

    Use mapToDecoder to produce the ObjectCodecs.

  4. -> ObjectCodec input output
#

Encode/decode a discriminated union of objects

The type of object being encoded/decoded is discriminated by a designated "discriminator" property on the object which takes a string value.

When encoding, the provided function is applied to the input to obtain a new encoder for the input. The function mapToEncoder is provided to assist with building these encoders. See examples in Usage.hs.

When decoding, the value of the discriminator property is looked up in the HashMap to obtain a decoder for the output. The function mapToDecoder is provided to assist with building these decoders. See examples in Usage.hs.

The HashMap is also used to generate schemas for the type. In particular, for OpenAPI 3, it will generate a schema with a discriminator, as defined by https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

API Note

This is a forward-compatible version of DiscriminatedUnionCodec.

discriminatedUnionCodec = 'DiscriminatedUnionCodec'

Mapping

valuedimapCodec
  1. :: (oldOutput -> newOutput)

    Function to make to the new type

  2. -> (newInput -> oldInput)

    Function to make from the new type

  3. -> Codec context oldInput oldOutput

    Codec for the old type

  4. -> Codec context newInput newOutput
#

Map both directions of a codec

You can use this function to change the type of a codec as long as the two functions are inverses.

HasCodec instance for newtypes

A good use-case is implementing HasCodec for newtypes:

newtype MyInt = MyInt { unMyInt :: Int }
instance HasCodec MyInt where
  codec = dimapCodec MyInt unMyInt codec
valuebimapCodec
  1. :: oldOutput -> Either String newOutput
  2. -> newInput -> oldInput
  3. -> Codec context oldInput oldOutput
  4. -> Codec context newInput newOutput
#

Map a codec's input and output types.

This function allows you to have the parsing fail in a new way.

If you use this function, then you will most likely want to add documentation about how not every value that the schema specifies will be accepted.

This function is like BimapCodec except it also combines one level of a nested BimapCodecs.

Example usage

logLevelCodec :: JSONCodec LogLevel logLevelCodec = bimapCodec parseLogLevel renderLogLevel codec ? "Valid values include DEBUG, INFO, WARNING, ERROR."

valuermapCodec
  1. :: oldOutput -> newOutput
  2. -> Codec context input oldOutput
  3. -> Codec context input newOutput
#

Map the output part of a codec

You can use this function if you only need to map the parsing-side of a codec. This function is probably only useful if the function you map does not change the codec type.

WARNING: This can be used to produce a codec that does not roundtrip.

Example1 expression
JSON.parseMaybe (parseJSONVia (rmapCodec (*2) codec)) (Number 5) :: Maybe IntJust 10
valuelmapCodec
  1. :: newInput -> oldInput
  2. -> Codec context oldInput output
  3. -> Codec context newInput output
#

Map the input part of a codec

You can use this function if you only need to map the rendering-side of a codec. This function is probably only useful if the function you map does not change the codec type.

WARNING: This can be used to produce a codec that does not roundtrip.

Example1 expression
toJSONVia (lmapCodec (*2) (codec :: JSONCodec Int)) 5Number 10.0

Composing codecs

valuemaybeCodec
  1. :: ValueCodec input output
  2. -> ValueCodec (Maybe input) (Maybe output)
#

Maybe codec

This can be used to also allow null during decoding of a Maybe value.

During decoding, also accept a null value as Nothing. During encoding, encode as usual.

Example usage
Example2 expressions
toJSONVia (maybeCodec codec) (Just 'a')String "a"toJSONVia (maybeCodec codec) (Nothing :: Maybe Char)Null
valuelistCodec :: ValueCodec input output -> ValueCodec [input] [output]
#

List codec

Build a codec for lists of values from a codec for a single value.

Example usage
Example1 expression
toJSONVia (listCodec codec) ['a','b']Array [String "a",String "b"]
API Note

This is the list version of vectorCodec.

valuenonEmptyCodec
  1. :: ValueCodec input output
  2. -> ValueCodec (NonEmpty input) (NonEmpty output)
#

Build a codec for nonempty lists of values from a codec for a single value.

Example usage
Example1 expression
toJSONVia (nonEmptyCodec codec) ('a' :| ['b'])Array [String "a",String "b"]
API Note

This is the non-empty list version of vectorCodec.

valuesingleOrListCodec :: ValueCodec input output -> ValueCodec [input] [output]
#

Single or list codec

This codec behaves like listCodec, except the values may also be simplified as a single value.

During parsing, a single element may be parsed as the list of just that element. During rendering, a list with only one element will be rendered as just that element.

Example usage
Example5 expressions
let c = singleOrListCodec codec :: JSONCodec [Int]toJSONVia c [5]Number 5.0toJSONVia c [5,6]Array [Number 5.0,Number 6.0]JSON.parseMaybe (parseJSONVia c) (Number 5) :: Maybe [Int]Just [5]JSON.parseMaybe (parseJSONVia c) (Array [Number 5, Number 6]) :: Maybe [Int]Just [5,6]
WARNING

If you use nested lists, for example when the given value codec is also a listCodec, you may get in trouble with ambiguities during parsing.

valuesingleOrNonEmptyCodec
  1. :: ValueCodec input output
  2. -> ValueCodec (NonEmpty input) (NonEmpty output)
#

Single or nonempty list codec

This codec behaves like nonEmptyCodec, except the values may also be simplified as a single value.

During parsing, a single element may be parsed as the list of just that element. During rendering, a list with only one element will be rendered as just that element.

Example usage
Example5 expressions
let c = singleOrNonEmptyCodec codec :: JSONCodec (NonEmpty Int)toJSONVia c (5 :| [])Number 5.0toJSONVia c (5 :| [6])Array [Number 5.0,Number 6.0]JSON.parseMaybe (parseJSONVia c) (Number 5) :: Maybe (NonEmpty Int)Just (5 :| [])JSON.parseMaybe (parseJSONVia c) (Array [Number 5, Number 6]) :: Maybe (NonEmpty Int)Just (5 :| [6])
WARNING

If you use nested lists, for example when the given value codec is also a nonEmptyCodec, you may get in trouble with ambiguities during parsing.

API Note

This is a nonempty version of singleOrListCodec.

valuevectorCodec
  1. :: ValueCodec input output
  2. -> ValueCodec (Vector input) (Vector output)
#

Vector codec

Build a codec for vectors of values from a codec for a single value.

Example usage
Example1 expression
toJSONVia (vectorCodec codec) (Vector.fromList ['a','b'])Array [String "a",String "b"]
API Note

This is a forward-compatible version of ArrayOfCodec without a name.

vectorCodec = ArrayOfCodec Nothing

Alternative parsing

valueparseAlternative
  1. :: Codec context input output

    Main codec, for parsing and rendering

  2. -> Codec context input' output

    Alternative codecs just for parsing

  3. -> Codec context input output
#

Like parseAlternatives, but with only one alternative codec

Example usage
Values
Example5 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)let c = parseAlternative shownBoundedEnumCodec (stringConstCodec [(Apple, "foo"), (Orange, "bar")])toJSONVia c AppleString "Apple"JSON.parseMaybe (parseJSONVia c) (String "foo") :: Maybe FruitJust AppleJSON.parseMaybe (parseJSONVia c) (String "Apple") :: Maybe FruitJust Apple
Required object fields
Example7 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)let c = shownBoundedEnumCodeclet o = parseAlternative (requiredFieldWith "current" c "current key for this field") (requiredFieldWith "legacy" c "legacy key for this field")toJSONObjectVia o ApplefromList [("current",String "Apple")]JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Apple")]) :: Maybe FruitJust AppleJSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("legacy",String "Apple")]) :: Maybe FruitJust AppleJSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Tomato")]) :: Maybe FruitNothing
Required object fields

While parseAlternative works exactly like you would expect it would with requiredField, using parseAlterternative with optional fields has some pitfalls.

Example6 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)let c = shownBoundedEnumCodeclet o = parseAlternative (optionalFieldWith "current" c "current key for this field") (optionalFieldWith "legacy" c "legacy key for this field")toJSONObjectVia o (Just Apple)fromList [("current",String "Apple")]toJSONObjectVia o NothingfromList []JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Apple")]) :: Maybe (Maybe Fruit)Just (Just Apple)

! This is the important result ! The second optionalFieldWith is not tried because the first one _succeeds_ in parsing Nothing

Example1 expression
JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("legacy",String "Apple")]) :: Maybe (Maybe Fruit)Just Nothing

Here the parser succeeds as well, because it fails to parse the current field, so it tries to parse the legacy field, which is missing.

Example1 expression
JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Tomato")]) :: Maybe (Maybe Fruit)Just Nothing
valueparseAlternatives
  1. :: Codec context input output

    Main codec, for parsing and rendering

  2. -> [Codec context input output]

    Alternative codecs just for parsing

  3. -> Codec context input output
#

Use one codec for the default way of parsing and rendering, but then also use a list of other codecs for potentially different parsing.

You can use this for keeping old ways of parsing intact while already rendering in the new way.

Example usage
Example6 expressions
data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)let c = parseAlternatives shownBoundedEnumCodec [stringConstCodec [(Apple, "foo"), (Orange, "bar")]]toJSONVia c AppleString "Apple"JSON.parseMaybe (parseJSONVia c) (String "foo") :: Maybe FruitJust AppleJSON.parseMaybe (parseJSONVia c) (String "Apple") :: Maybe FruitJust AppleJSON.parseMaybe (parseJSONVia c) (String "Tomato") :: Maybe FruitNothing

Choice

valuematchChoiceCodec
  1. :: Codec context input output

    First codec

  2. -> Codec context input' output

    Second codec

  3. -> (newInput -> Either input input')

    Rendering chooser

  4. -> Codec context newInput output
#

A choice codec, but unlike eitherCodec, it's for the same output type instead of different ones.

While parsing, this codec will first try the left codec, then the right if that fails.

While rendering, the provided function is used to decide which codec to use for rendering.

Note: The reason this is less primitive than the eitherCodec is that Either makes it clear which codec you want to use for rendering. In this case, we need to provide our own function for choosing which codec we want to use for rendering.

Example usage
Example1 expression
:{  let c =       matchChoiceCodec        (literalTextCodec "even")        (literalTextCodec "odd")        (\s -> if s == "even" then Left s else Right s):}
Example4 expressions
toJSONVia c "even"String "even"toJSONVia c "odd"String "odd"JSON.parseMaybe (parseJSONVia c) (String "even") :: Maybe TextJust "even"JSON.parseMaybe (parseJSONVia c) (String "odd") :: Maybe TextJust "odd"
API Note

This is a forward-compatible version of 'matchChoiceCodecAs PossiblyJointUnion':

disjointMatchChoiceCodec = matchChoiceCodecAs PossiblyJointUnion
valuedisjointMatchChoiceCodec
  1. :: Codec context input output

    First codec

  2. -> Codec context input' output

    Second codec

  3. -> (newInput -> Either input input')

    Rendering chooser

  4. -> Codec context newInput output
#

Disjoint version of matchChoiceCodec

API Note

This is a forward-compatible version of 'matchChoiceCodecAs DisjointUnion':

disjointMatchChoiceCodec = matchChoiceCodecAs DisjointUnion
valuematchChoicesCodec
  1. :: [(input -> Maybe input, Codec context input output)]

    Codecs, each with their own rendering matcher

  2. -> Codec context input output

    Fallback codec, in case none of the matchers in the list match

  3. -> Codec context input output
#

A choice codec for a list of options, each with their own rendering matcher.

During parsing, each of the codecs are tried from first to last until one succeeds.

During rendering, each matching function is tried until either one succeeds and the corresponding codec is used, or none succeed and the fallback codec is used.

Example usage
Example1 expression
:{  let c =       matchChoicesCodec         [ (\s -> if s == "even" then Just s else Nothing, literalTextCodec "even")         , (\s -> if s == "odd" then Just s else Nothing, literalTextCodec "odd")         ] (literalTextCodec "fallback"):}
Example7 expressions
toJSONVia c "even"String "even"toJSONVia c "odd"String "odd"toJSONVia c "foobar"String "fallback"JSON.parseMaybe (parseJSONVia c) (String "even") :: Maybe TextJust "even"JSON.parseMaybe (parseJSONVia c) (String "odd") :: Maybe TextJust "odd"JSON.parseMaybe (parseJSONVia c) (String "foobar") :: Maybe TextNothingJSON.parseMaybe (parseJSONVia c) (String "fallback") :: Maybe TextJust "fallback"
API Note

This is a forward-compatible version of 'matchChoicesCodecAs DisjointUnion'.

disjointMatchChoiceCodec = matchChoicesCodecAs DisjointUnion
valuedisjointMatchChoicesCodec
  1. :: [(input -> Maybe input, Codec context input output)]

    Codecs, each with their own rendering matcher

  2. -> Codec context input output

    Fallback codec, in case none of the matchers in the list match

  3. -> Codec context input output
#

Disjoint version of matchChoicesCodec

API Note

This is a forward-compatible version of 'matchChoicesCodecAs DisjointUnion'.

disjointMatchChoiceCodec = matchChoicesCodecAs DisjointUnion

Adding documentation to a codec

value(<??>)
  1. :: ValueCodec input output
  2. -> [Text]

    Lines of comments

  3. -> ValueCodec input output
#

A version of <?> that lets you supply a list of lines of text instead of a single text.

This helps when you use an automated formatter that deals with lists more nicely than with multi-line strings.

Bare codec

5 declarations
datadata Codec context input output where
#

A Self-documenting encoder and decoder,

also called an Autodocodec.

In an ideal situation, this type would have only one type parameter: 'Codec value'. This does not work very well because we want to be able to implement Functor and Applicative, which each require a kind '* -> *'. So instead we use two type parameters.

The two type parameters correspond to the phase in which they are used:

  • The input parameter is used for the type that is used during encoding of a value, so it's the input to the codec.

  • The output parameter is used for the type that is used during decoding of a value, so it's the output of the codec.

  • Both parameters are unused during documentation.

Constructors

Instances2Applicative, Functor
valuepureCodec :: output -> ObjectCodec input output
#

Produce a value without parsing any part of an Object.

This function exists to implement Applicative (ObjectCodec input).

API Note

This is a forward-compatible version of PureCodec.

pureCodec = PureCodec
valueapCodec
  1. :: ObjectCodec input (output -> newOutput)
  2. -> ObjectCodec input output
  3. -> ObjectCodec input newOutput
#

Sequentially apply two codecs that parse part of an Object.

This function exists to implement Applicative (ObjectCodec input).

API Note

This is a forward-compatible version of ApCodec.

apCodec = ApCodec

Deriving Via

1 declaration
newtypenewtype Autodocodec a
#

Autodocodec is a wrapper to provide codec-based deriving strategies.

Example usage
data Via = Via {viaOne :: !Text, viaTwo :: !Text}
  deriving stock (Show, Eq, Generic)
  deriving (FromJSON, ToJSON) via (Autodocodec Via)

instance HasCodec Via where
  codec =
    object "Via" $
      Via
        <$> requiredField "one" "first field" .= viaOne
        <*> requiredField "two" "second field" .= viaTwo

Constructors

Instances2FromJSON, ToJSON

Internals you most likely don't need

valueshowCodecABit :: Codec context input output -> String
#

Show a codec to a human.

This function exists for codec debugging. It omits any unshowable information from the output.

To make sure we definitely export everything

0 declarations