dhall-json-1.3.0: Convert between Dhall and JSON or YAML

Safe HaskellNone
LanguageHaskell98

Dhall.JSONToDhall

Contents

Description

Convert JSON data to Dhall given a Dhall type expression necessary to make the translation unambiguous.

Reasonable requirements for conversion are:

  1. The Dhall type expression t passed as an argument to json-to-dhall should be a valid type of the resulting Dhall expression
  2. A JSON data produced by the corresponding dhall-to-json from the Dhall expression of type t should (under reasonable assumptions) reproduce the original Dhall expression using json-to-dhall with type argument t

Only a subset of Dhall types consisting of all the primitive types as well as Optional, Union and Record constructs, is used for reading JSON data:

  • Bools
  • Naturals
  • Integers
  • Doubles
  • Texts
  • Lists
  • Optional values
  • unions
  • records

This library can be used to implement an executable which takes any data serialisation format which can be parsed as an Aeson Value and converts the result to a Dhall value. One such executable is json-to-dhall which is used in the examples below.

Primitive types

JSON Bools translate to Dhall bools:

$ json-to-dhall Bool <<< 'true'
True
$ json-to-dhall Bool <<< 'false'
False

JSON numbers translate to Dhall numbers:

$ json-to-dhall Integer <<< 2
+2
$ json-to-dhall Natural <<< 2
2
$ json-to-dhall Double <<< -2.345
-2.345

Dhall Text corresponds to JSON text:

$ json-to-dhall Text <<< '"foo bar"'
"foo bar"

Lists and records

Dhall Lists correspond to JSON lists:

$ json-to-dhall 'List Integer' <<< '[1, 2, 3]'
[ +1, +2, +3 ]

Dhall records correspond to JSON records:

$ json-to-dhall '{foo : List Integer}' <<< '{"foo": [1, 2, 3]}'
{ foo = [ +1, +2, +3 ] }

Note, that by default, only the fields required by the Dhall type argument are parsed (as you commonly will not need all the data), the remaining ones being ignored:

$ json-to-dhall '{foo : List Integer}' <<< '{"foo": [1, 2, 3], "bar" : "asdf"}'
{ foo = [ +1, +2, +3 ] }

If you do need to make sure that Dhall fully reflects JSON record data comprehensively, --records-strict flag should be used:

$ json-to-dhall --records-strict '{foo : List Integer}' <<< '{"foo": [1, 2, 3], "bar" : "asdf"}'
Error: Key(s) @bar@ present in the JSON object but not in the corresponding Dhall record. This is not allowed in presence of --records-strict:

By default, JSON key-value arrays will be converted to Dhall records:

$ json-to-dhall '{ a : Integer, b : Text }' <<< '[{"key":"a", "value":1}, {"key":"b", "value":"asdf"}]'
{ a = +1, b = "asdf" }

Attempting to do the same with --no-keyval-arrays on will result in error:

$ json-to-dhall --no-keyval-arrays '{ a : Integer, b : Text }' <<< '[{"key":"a", "value":1}, {"key":"b", "value":"asdf"}]'
Error: JSON (key-value) arrays cannot be converted to Dhall records under --no-keyval-arrays flag:

Conversion of the homogeneous JSON maps to the corresponding Dhall association lists by default:

$ json-to-dhall 'List { mapKey : Text, mapValue : Text }' <<< '{"foo": "bar"}'
[ { mapKey = "foo", mapValue = "bar" } ]

Flag --no-keyval-maps switches off this mechanism (if one would ever need it):

$ json-to-dhall --no-keyval-maps 'List { mapKey : Text, mapValue : Text }' <<< '{"foo": "bar"}'
Error: Homogeneous JSON map objects cannot be converted to Dhall association lists under --no-keyval-arrays flag

Optional values and unions

Dhall Optional Dhall type allows null or missing JSON values:

$ json-to-dhall "Optional Integer" <<< '1'
Some +1
$ json-to-dhall "Optional Integer" <<< null
None Integer
$ json-to-dhall '{ a : Integer, b : Optional Text }' <<< '{ "a": 1 }'

{ a = +1, b = None Text }

For Dhall union types the correct value will be based on matching the type of JSON expression:

$ json-to-dhall 'List < Left : Text | Right : Integer >' <<< '[1, "bar"]'
[ < Left : Text | Right : Integer >.Right +1

, : Text | Right : Integer.Left "bar" ]

$ json-to-dhall '{foo : < Left : Text | Right : Integer >}' <<< '{ "foo": "bar" }'
{ foo = < Left : Text | Right : Integer >.Left "bar" }

In presence of multiple potential matches, the first will be selected by default:

$ json-to-dhall '{foo : < Left : Text | Middle : Text | Right : Integer >}' <<< '{ "foo": "bar"}'
{ foo = < Left : Text | Middle : Text | Right : Integer >.Left "bar" }

This will result in error if --unions-strict flag is used, with the list of alternative matches being reported (as a Dhall list)

$ json-to-dhall --unions-strict '{foo : < Left : Text | Middle : Text | Right : Integer >}' <<< '{ "foo": "bar"}'
Error: More than one union component type matches JSON value
...
Possible matches:

: Text | Middle : Text | Right : Integer.Left "bar" > -------- : Text | Middle : Text | Right : Integer.Middle "bar"

Synopsis

JSON to Dhall

parseConversion :: Parser Conversion Source #

Standard parser for options related to the conversion method

data Conversion Source #

JSON-to-dhall translation options

Constructors

Conversion 

Fields

Instances
Show Conversion Source # 
Instance details

Defined in Dhall.JSONToDhall

defaultConversion :: Conversion Source #

Default conversion options

resolveSchemaExpr Source #

Arguments

:: Text

type code (schema)

-> IO ExprX 

Parse schema code to a valid Dhall expression and check that its type is actually Type

typeCheckSchemaExpr :: (Exception e, MonadCatch m) => (CompileError -> e) -> ExprX -> m ExprX Source #

Check that the Dhall type expression actually has type Type >>> :set -XOverloadedStrings >>> import Dhall.Core

>>> typeCheckSchemaExpr id =<< resolveSchemaExpr "List Natural"
App List Natural
>>> typeCheckSchemaExpr id =<< resolveSchemaExpr "+1"
*** Exception:
Error: Schema expression is succesfully parsed but has Dhall type:
Integer
Expected Dhall type: Type
Parsed expression: +1

dhallFromJSON :: Conversion -> ExprX -> Value -> Either CompileError ExprX Source #

The main conversion function. Traversingzipping Dhall type and Aeson value trees together to produce a Dhall term/ tree, given Conversion options:

>>> :set -XOverloadedStrings
>>> import qualified Dhall.Core as D
>>> import qualified Dhall.Map as Map
>>> import qualified Data.Aeson as A
>>> import qualified Data.HashMap.Strict as HM
>>> s = D.Record (Map.fromList [("foo", D.Integer)])
>>> v = A.Object (HM.fromList [("foo", A.Number 1)])
>>> dhallFromJSON defaultConversion s v
Right (RecordLit (fromList [("foo",IntegerLit 1)]))

Exceptions