json-spec-elm: Elm code generate for `json-spec`.

[ elm, json, library, mit ] [ Propose Tags ]

Produce elm types, encoders, and decoders from a `json-spec` Specification.

See `testtest.hs` for an example.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Flags

Automatic Flags
NameDescriptionDefault
compile-elm

Set this flag to run the Elm compilation tests, which requires Elm to be installed on the system.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.1, 0.2.0.0, 0.3.0.0, 0.3.0.1, 0.3.0.2, 0.3.0.3, 0.3.0.4, 0.4.0.0, 0.4.0.1 (info)
Dependencies base (>=4.17.2.1 && <4.20), bound (>=2.0.7 && <2.1), containers (>=0.6.7 && <0.7), elm-syntax (>=0.3.2.0 && <0.4), json-spec (>=0.3.0.0 && <0.4), mtl (>=2.2.2 && <2.4), text (>=2.0.2 && <2.2) [details]
License MIT
Copyright 2024 Rick Owens
Author Rick Owens
Maintainer rick@owensmurray.com
Category JSON, Elm
Home page https://github.com/owensmurray/json-spec-elm
Uploaded by rickowens at 2024-04-05T23:39:50Z
Distributions Stackage:0.4.0.1
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 204 total (46 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for json-spec-elm-0.4.0.1

[back to package description]

json-spec-elm

Produce elm types, encoders, and decoders from a json-spec Specification.

See /test/test.hs for an example.

Example

First let's define an example spec.

A couple of things to note:

  1. Only things that are named using JsonLet will get elm types. (Also note, Named is just an alias for JsonLet.) So you will probably want to name the top-level of your spec at least.
  2. JsonEither spec types must be named. Elm can support anonymous record types but not anonymous sum types, so there is no way to embed an anonymous JsonEither. You have to give it a name.
  3. Naming the constructors for sum types is a little tricky. If a branch of JsonEither is given a name (using Named), then we interpret the name as the name of the data constructor, not the name of the type contained within the branch. To name both the data constructor and the type, you must use nested Nameds.
type ExampleSpec =
  Named "ExampleType"
    ( JsonObject
        '[ '("stringField", JsonString)
         , '( "anonymousObject"
            , JsonObject
                '[ '("floatField", JsonNum)
                 , '("dateField", JsonDateTime)
                 , '( "sumType1"
                    , Named "SumTypeWithCustomConstructorNames"
                        ( JsonEither
                            ( JsonEither
                                (Named "IntConstructor" JsonInt)
                                (Named "StringConstructor" JsonString)
                            )
                            (Named "FloatConstructor" JsonNum)
                        )
                    )
                 , '( "sumType2"
                    , Named "SumTypeWithAutomaticConstructorNames"
                        ( JsonEither
                            ( JsonEither
                                JsonInt
                                JsonString
                            )
                            JsonNum
                        )
                    )
                 ]
            )
         , '( "namedObject"
            , Named "NamedElmRecord"
                ( JsonObject
                    '[ '("stringField", JsonString)
                     , '( "listOfStrings"
                        , JsonArray JsonString
                        )
                     ]
                )
            )
         ]
    )

This spec will produce the following code (after running it through elm-format):

module Api.Data exposing
  ( ExampleType
  , NamedElmRecord
  , SumTypeWithAutomaticConstructorNames(..)
  , SumTypeWithCustomConstructorNames(..)
  , exampleTypeDecoder
  , exampleTypeEncoder
  , namedElmRecordDecoder
  , namedElmRecordEncoder
  , sumTypeWithAutomaticConstructorNamesDecoder
  , sumTypeWithAutomaticConstructorNamesEncoder
  , sumTypeWithCustomConstructorNamesDecoder
  , sumTypeWithCustomConstructorNamesEncoder
  )

import Iso8601
import Json.Decode
import Json.Encode
import Time


exampleTypeDecoder : Json.Decode.Decoder ExampleType
exampleTypeDecoder =
  Json.Decode.succeed
    (\a b c ->
      { stringField = a
      , anonymousObject = b
      , namedObject = c
      }
    )
    |> Json.Decode.andThen (\a -> Json.Decode.map a Json.Decode.string)
    |> Json.Decode.andThen
        (\a ->
          Json.Decode.map a
            (Json.Decode.succeed
              (\b c d e ->
                { floatField = b
                , dateField = c
                , sumType1 = d
                , sumType2 = e
                }
              )
              |> Json.Decode.andThen (\b -> Json.Decode.map b Json.Decode.float)
              |> Json.Decode.andThen (\b -> Json.Decode.map b Iso8601.decoder)
              |> Json.Decode.andThen (\b -> Json.Decode.map b sumTypeWithCustomConstructorNamesDecoder)
              |> Json.Decode.andThen (\b -> Json.Decode.map b sumTypeWithAutomaticConstructorNamesDecoder)
            )
        )
    |> Json.Decode.andThen (\a -> Json.Decode.map a namedElmRecordDecoder)


exampleTypeEncoder : ExampleType -> Json.Encode.Value
exampleTypeEncoder a =
  Json.Encode.object
    [ ( "stringField", Json.Encode.string a.stringField )
    , ( "anonymousObject"
      , (\b ->
          Json.Encode.object
            [ ( "floatField", Json.Encode.float b.floatField )
            , ( "dateField", Iso8601.encode b.dateField )
            , ( "sumType1", sumTypeWithCustomConstructorNamesEncoder b.sumType1 )
            , ( "sumType2", sumTypeWithAutomaticConstructorNamesEncoder b.sumType2 )
            ]
        )
          a.anonymousObject
      )
    , ( "namedObject", namedElmRecordEncoder a.namedObject )
    ]


namedElmRecordDecoder : Json.Decode.Decoder NamedElmRecord
namedElmRecordDecoder =
  Json.Decode.succeed (\a b -> { stringField = a, listOfStrings = b })
    |> Json.Decode.andThen (\a -> Json.Decode.map a Json.Decode.string)
    |> Json.Decode.andThen (\a -> Json.Decode.map a (Json.Decode.list Json.Decode.string))


namedElmRecordEncoder : NamedElmRecord -> Json.Encode.Value
namedElmRecordEncoder a =
  Json.Encode.object
    [ ( "stringField", Json.Encode.string a.stringField )
    , ( "listOfStrings", Json.Encode.list Json.Encode.string a.listOfStrings )
    ]


sumTypeWithAutomaticConstructorNamesDecoder : Json.Decode.Decoder SumTypeWithAutomaticConstructorNames
sumTypeWithAutomaticConstructorNamesDecoder =
  Json.Decode.oneOf
    [ Json.Decode.map SumTypeWithAutomaticConstructorNames_1 Json.Decode.int
    , Json.Decode.map SumTypeWithAutomaticConstructorNames_2 Json.Decode.string
    , Json.Decode.map SumTypeWithAutomaticConstructorNames_3 Json.Decode.float
    ]


sumTypeWithAutomaticConstructorNamesEncoder : SumTypeWithAutomaticConstructorNames -> Json.Encode.Value
sumTypeWithAutomaticConstructorNamesEncoder a =
  case a of
    SumTypeWithAutomaticConstructorNames_1 b ->
      Json.Encode.int b

    SumTypeWithAutomaticConstructorNames_2 b ->
      Json.Encode.string b

    SumTypeWithAutomaticConstructorNames_3 b ->
      Json.Encode.float b


sumTypeWithCustomConstructorNamesDecoder : Json.Decode.Decoder SumTypeWithCustomConstructorNames
sumTypeWithCustomConstructorNamesDecoder =
  Json.Decode.oneOf
    [ Json.Decode.map IntConstructor Json.Decode.int
    , Json.Decode.map StringConstructor Json.Decode.string
    , Json.Decode.map FloatConstructor Json.Decode.float
    ]


sumTypeWithCustomConstructorNamesEncoder : SumTypeWithCustomConstructorNames -> Json.Encode.Value
sumTypeWithCustomConstructorNamesEncoder a =
  case a of
    IntConstructor b ->
      Json.Encode.int b

    StringConstructor b ->
      Json.Encode.string b

    FloatConstructor b ->
      Json.Encode.float b


type SumTypeWithAutomaticConstructorNames
  = SumTypeWithAutomaticConstructorNames_1 Int
  | SumTypeWithAutomaticConstructorNames_2 String
  | SumTypeWithAutomaticConstructorNames_3 Float


type SumTypeWithCustomConstructorNames
  = IntConstructor Int
  | StringConstructor String
  | FloatConstructor Float


type alias ExampleType =
  { stringField : String
  , anonymousObject :
      { floatField : Float
      , dateField : Time.Posix
      , sumType1 : SumTypeWithCustomConstructorNames
      , sumType2 : SumTypeWithAutomaticConstructorNames
      }
  , namedObject : NamedElmRecord
  }


type alias NamedElmRecord =
  { stringField : String, listOfStrings : List String }

Generating code

The main function exposed by this module is elmDefs, which returns a Set Definition (where Definition is from the elm-syntax package). For examples on how to transform a Definition into some files on disk, see /test/test.hs.