Search code examples
dhall

Encoding recursive types to JSON using Dhall


A simplified version of my desired output:

{
  "dynamic-name": {
    "type": "type",
    "fields": {
      "inner-dynamic-name": {
        "type": "inner-type",
        "analyzer": "analyzer"
      }
    }
  }
}

And here's the Dhall code I've written to generate that:

let Field
    : Type
    = ∀(Field : Type) →
      ∀ ( Leaf
        : { mapKey : Text, mapValue : { type : Text, analyzer : Optional Text } } →
            Field
        ) →
      ∀ ( Node
        : { mapKey : Text, mapValue : { type : Text, fields : List Field } } →
            Field
        ) →
        Field

let example
    : Field
    = λ(Field : Type) →
      λ ( Leaf
        : { mapKey : Text, mapValue : { type : Text, analyzer : Optional Text } } →
            Field
        ) →
      λ ( Node
        : { mapKey : Text, mapValue : { type : Text, fields : List Field } } →
            Field
        ) →
        Node
          { mapKey = "dynamic-name"
          , mapValue =
            { type = "type"
            , fields =
              [ Leaf
                  { mapKey = "inner-dynamic-name"
                  , mapValue =
                    { type = "inner-type", analyzer = Some "analyzer" }
                  }
              ]
            }
          }

in  example

However, when passing my Dhall configuration to dhall-to-json I get the following error:

Error: Cannot translate to JSON                                            
                                                                                
Explanation: Only primitive values, records, unions, ❰List❱s, and ❰Optional❱    
values can be translated from Dhall to JSON                                     
                                                                                
The following Dhall expression could not be translated to JSON:                 
                                                                                
↳ λ(_ : Type) →
  λ ( _
    : { mapKey : Text, mapValue : { analyzer : Optional Text, type : Text } } →
        _@1
    ) →
  λ(_ : { mapKey : Text, mapValue : { fields : List _@1, type : Text } } → _@2) →
    _
      { mapKey = "dynamic-name"
      , mapValue =
        { fields =
          [ _@1
              { mapKey = "inner-dynamic-name"
              , mapValue = { analyzer = Some "analyzer", type = "inner-type" }
              }
          ]
        , type = "type"
        }
      }

I'm running version 1.7.6 of dhall-to-json. What am I doing wrong?

(Disregard: I need to include more words in order to be allowed to post my question, but anything more seems superfluous. These last sentences is me being Hackerman.)


Solution

  • I'm copying my answer from https://github.com/dhall-lang/dhall-haskell/issues/2259 to here:

    dhall-to-json cannot handle custom recursive types directly, but it can handle one distinguished recursive type, which is Prelude.JSON.Type, so something like this would work:

    let JSON =
          https://prelude.dhall-lang.org/JSON/package.dhall
            sha256:5f98b7722fd13509ef448b075e02b9ff98312ae7a406cf53ed25012dbc9990ac
    
    let Field
        : Type
        = ∀(Field : Type) →
          ∀ ( Leaf
            : { mapKey : Text
              , mapValue : { type : Text, analyzer : Optional Text }
              } →
                Field
            ) →
          ∀ ( Node
            : { mapKey : Text, mapValue : { type : Text, fields : List Field } } →
                Field
            ) →
            Field
    
    let example
        : Field
        = λ(Field : Type) →
          λ ( Leaf
            : { mapKey : Text
              , mapValue : { type : Text, analyzer : Optional Text }
              } →
                Field
            ) →
          λ ( Node
            : { mapKey : Text, mapValue : { type : Text, fields : List Field } } →
                Field
            ) →
            Node
              { mapKey = "dynamic-name"
              , mapValue =
                { type = "type"
                , fields =
                  [ Leaf
                      { mapKey = "inner-dynamic-name"
                      , mapValue =
                        { type = "inner-type", analyzer = Some "analyzer" }
                      }
                  ]
                }
              }
    
    let Field/toJSON
        : Field → JSON.Type
        = λ(field : Field) →
            field
              JSON.Type
              ( λ ( args
                  : { mapKey : Text
                    , mapValue : { type : Text, analyzer : Optional Text }
                    }
                  ) →
                  JSON.object
                    [ { mapKey = args.mapKey
                      , mapValue =
                          JSON.object
                            ( toMap
                                { type = JSON.string args.mapValue.type
                                , analyzer =
                                    merge
                                      { None = JSON.null, Some = JSON.string }
                                      args.mapValue.analyzer
                                }
                            )
                      }
                    ]
              )
              ( λ ( args
                  : { mapKey : Text
                    , mapValue : { type : Text, fields : List JSON.Type }
                    }
                  ) →
                  JSON.object
                    [ { mapKey = args.mapKey
                      , mapValue =
                          JSON.object
                            ( toMap
                                { type = JSON.string args.mapValue.type
                                , fields = JSON.array args.mapValue.fields
                                }
                            )
                      }
                    ]
              )
    
    in  Field/toJSON example
    

    … and dhall-to-json accepts that:

    $ dhall-to-json --file ./example.dhall
    
    {
      "dynamic-name": {
        "fields": [
          {
            "inner-dynamic-name": {
              "analyzer": "analyzer",
              "type": "inner-type"
            }
          }
        ],
        "type": "type"
      }
    }