dhall-lang / dhall-json

This repository has moved to https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-json
BSD 3-Clause "New" or "Revised" License
65 stars 6 forks source link

Heterogenous JSON array targets / JSON AST target? #37

Closed mbj closed 6 years ago

mbj commented 6 years ago

While trying to encode to the following JSON structure I end up with the need to encode a JSON heterogenous array:

{"Fn::Select": [0, {"Fn::GetAZs": {"Ref": "AWS::Region"}}]}

This is part of the Cloudformation intrinsic function syntax. See the reference here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-select.html

It asks for a 2 element array where the first item is a JSON number and the 2nd is a array of arbitrary JSON values.

Currently, and please correct me: There is no dhall value possible that dhall-json would generate this two element JSON array?

Assuming the above question indeed gets answered with: No. I see two options to move on:

Another approach

Context:

The JSON syntax allows to specify a lot of "odd" data structures. There may be target schemas that require explicit null values, or other structures we cannot easily find a dhall equivalent for. Adding special cases to dhall-json may be the wrong approach to handle them all. Also in many cases the special case may be "too special" to warrant another addition to dhall-json.

So what if dhall-json (at the dhall language level!) came with an implementation of a JSON AST? Types shipped with a dhall-json specific prelude the dhall-json Haskell implementation maps directly to aeson Value?

The nice benefit of this approach is that authors like me can keep their dhall values as compact as possible, not infecting them with JSON specific structure "early on" and only in the very last step can write some still dhall level conversion functions to that said AST.

I think this approach can likely eliminate the need to drop down to custom Haskell in many cases.

For now I'll go ahead and implement my own cloudformation specific special cases. Still I'd love to get critical feedback on this idea.

Gabriella439 commented 6 years ago

@mbj: There is a way to do this. If you use a sum type to unify the two mismatching types then dhall-to-json will strip the tag when converting to JSON. For example:

-- example.dhall

    let Union = < Left : Natural | Right : { `Fn::GetAZs` : { Ref : Text } } >

in  let union = constructors Union

in  { `Fn::Select` =
        [ union.Left 0, union.Right { `Fn::GetAZs` = { Ref = "AWS::Region" } } ]
    }
$ dhall-to-json <<< './example.dhall'
{"Fn::Select":[0,{"Fn::GetAZs":{"Ref":"AWS::Region"}}]}
mbj commented 6 years ago

@Gabriel439 Thanks. This allows me to remove some explicit Haskell already.

The problem is that the dhall type allows me to flip the first and the 2nd element around without violating the type check. Ideally I was able to specify both fields with their different types explicitly?

Gabriella439 commented 6 years ago

@mbj: You can wrap it in a function that enforces that invariant, like this:

-- example.dhall

    let Union = < Left : Natural | Right : { `Fn::GetAZs` : { Ref : Text } } >

in  let union = constructors Union

in  let select =
            λ(x : Natural)
          → λ(y : { `Fn::GetAZs` : { Ref : Text } })
          → { `Fn::Select` = [ union.Left x, union.Right y ] }

in  select 0 { `Fn::GetAZs` = { Ref = "AWS::Region" } }
mbj commented 6 years ago

@Gabriel439 I like that, thank you.

I'll close this issue for now.

Gabriella439 commented 6 years ago

You're welcome! 🙂