| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Yamlet.Aeson
Contents
Description
Use the FromJSON and ToJSON instances of aeson
with yamlet, for a type that has no FromYaml and ToYaml instances yet,
e.g. in a program that moves from the yaml package to yamlet.
The examples use these external imports:
>>>import Data.Aeson qualified as A>>>import Data.Map.Strict qualified as M>>>import Data.Text qualified as T>>>import Data.Text.IO qualified as T
A value in ViaAeson decodes and encodes with the
FromJSON and ToJSON instances:
>>>:{data Server = Server {port :: Int, host :: T.Text} deriving stock (Generic, Show) deriving anyclass (A.FromJSON) instance A.ToJSON Server where toEncoding = A.genericToEncoding A.defaultOptions :}
>>>Right (ViaAeson server) = decodeText @(ViaAeson Server) "port: 80\nhost: localhost\n"
>>>serverServer {port = 80, host = "localhost"}
>>>T.putStr (encodeText (ViaAeson server))port: 80 host: localhost
A type with FromJSON and ToJSON instances can
derive its FromYaml and ToYaml instances via ViaAeson, e.g. in a
program that reads and writes both JSON and YAML and keeps one set of
instances. Such a type can be a field of a type with instances of its own:
>>>:{newtype Address = Address T.Text deriving stock (Show) deriving newtype (A.FromJSON, A.ToJSON) deriving (FromYaml, ToYaml) via ViaAeson Address data Mail = Mail {from :: Address, to :: [Address]} deriving stock (Generic, Show) deriving anyclass (GenericYamlOptions) deriving (FromYaml, ToYaml) via GenericYaml Mail :}
>>>decodeText @Mail "from: a@example.com\nto: [b@example.com]\n"Right (Mail {from = Address "a@example.com", to = [Address "b@example.com"]})
An error of a decoder of aeson points to the node that caused it:
>>>input = "- port: 80\n host: a\n- port: http\n host: b\n"
>>>T.putStr input- port: 80 host: a - port: http host: b
>>>either printErrors print (decodeText @(ViaAeson [Server]) input)input.yaml:3:9: [1].port: parsing Int failed, expected Number, but encountered String | 3 | - port: http | ^
The module also has the FromYaml and ToYaml instances for an aeson
Value, e.g. for a field that holds any data. They convert as
the section Conversion says:
>>>decodeText @A.Value "name: a\nports: [80, 443]\n"Right (Object (fromList [("name",String "a"),("ports",Array [Number 80.0,Number 443.0])]))
Order of keys
The encoder writes the keys of a mapping in the order of
toEncoding. That is the order of the fields if the instance
defines toEncoding, e.g. with genericToEncoding as
Server above, or with deriveJSON. The default
toEncoding goes through toJSON, so the keys come
in the order of an aeson object, which is sorted by default.
parseJSON gets the keys in no order, because an aeson object
has none. To keep the order of a mapping, decode the mapping with a decoder
of yamlet. Its values can still decode via ViaAeson:
>>>:{newtype Servers = Servers [(T.Text, Server)] deriving stock (Show) instance FromYaml Servers where parseYaml = withMapping $ \o -> Servers <$> traverse ( \(k, v) -> (,) <$> parseYaml k <*> fmap (.value) (parseYaml @(ViaAeson Server) v) ) (objectEntries o) :}
>>>decodeText @Servers "web: {port: 80, host: a}\napi: {port: 81, host: b}\n"Right (Servers [("web",Server {port = 80, host = "a"}),("api",Server {port = 81, host = "b"})])
decodeWithDocument keeps the whole document with the decoded
value, e.g. to write the document back with a change.
Conversion
A YAML document converts to an aeson Value as follows:
- A key is the text of its scalar, e.g.
"0x10"for0x10and"~"for~, as in the yaml package. Two keys with the same text are an error, e.g.1and"1", which are different keys in YAML. A key that is a collection is an error. - A key
<<is an ordinary key, because the merge keys of YAML 1.1 are not supported. .infand-.infare the strings"+inf"and"-inf", and.nanis null. TheFromJSONandToJSONinstances forDoubleandFloatread and write these values.-0.0is the number 0, because aScientifichas no negative zero.- A number whose exponent in scientific notation is beyond the range
from -1000 to 1000, e.g.
1e1001, is an error, as in yamlet. ANumberconverts to YAML as aeson writes it in JSON: as an integer if itsbase10Exponentis from 0 to 1024, otherwise in scientific notation. Thus1e1001reads back as an integer, but1e1025and1e-1001do not read back. - A tag that is not of the core schema makes a scalar a string, e.g.
!secret 123is the string"123". The yaml package reads it as the number 123. A collection with such a tag converts as without it, e.g.!point {x: 1}is the object{"x": 1}.
A FromJSON instance can convert two different keys to the
same key and then keep only one of the pairs, as it does for JSON, e.g. 1
and 1.0 for a Map Int. The FromYaml instances for maps reject such
keys.
>>>decodeText @(ViaAeson (M.Map Int T.Text)) "1: a\n1.0: b\n"Right (ViaAeson {value = fromList [(1,"a")]})
An aeson Value converts to YAML as aeson writes it in JSON,
e.g. the keys of a Map Int are strings, which the encoder quotes because
they look like numbers:
>>>T.putStr (encodeText (ViaAeson (M.fromList @Int @T.Text [(1, "a")])))'1': a
Documentation
A value that decodes and encodes with its FromJSON and
ToJSON instances. The field has no selector function, so read
it with record dot syntax, e.g. (.value), or with a pattern.
Instances
| Show a => Show (ViaAeson a) Source # | |
| Eq a => Eq (ViaAeson a) Source # | |
| Ord a => Ord (ViaAeson a) Source # | |
| FromJSON a => FromYaml (ViaAeson a) Source # | The value with the An error of a key of a map points to the value of the key, because aeson gives it the same path as an error of the value. The path at the start of the message names the key:
|
Defined in Yamlet.Aeson Methods parseYaml :: Node -> Parser (ViaAeson a) parseYamlList :: Node -> Parser [ViaAeson a] parseYamlField :: Node -> Node -> Parser (ViaAeson a) | |
| ToJSON a => ToYaml (ViaAeson a) Source # | The value with the If the encoding is not valid JSON, which only
|
Defined in Yamlet.Aeson | |
Orphan instances
| FromYaml Value Source # | The value of a node, converted as the section Conversion says. |
Methods parseYaml :: Node -> Parser Value parseYamlList :: Node -> Parser [Value] parseYamlField :: Node -> Node -> Parser Value | |
| ToYaml Value Source # | The value as aeson writes it in JSON. |
Methods toYaml :: Value -> Node toYamlList :: [Value] -> Node toYamlField :: Node -> Value -> (Node, Node) | |