• Stars
    star
    83
  • Rank 379,822 (Top 8 %)
  • Language
    Elixir
  • License
    MIT License
  • Created about 7 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generates Elm types, JSON decoders, JSON encoders and fuzz tests from JSON schema specifications

JSON Schema to Elm

Description

Generates Elm types, JSON decoders, JSON encoders, and Fuzz tests from JSON schema specifications.

Only supports - a subset of - JSON Schema draft v7.

Installation

This project requires that you already have elixir and its build tool mix installed, this can be done with brew install elixir or similar.

  • Download latest release at: https://github.com/dragonwasrobot/json-schema-to-elm/releases, or
  • clone this repository: git clone [email protected]:dragonwasrobot/json-schema-to-elm.git, then
  • build an executable: MIX_ENV=prod mix build (Windows cmd.exe: set "MIX_ENV=prod" && mix build), and
  • run the executable, ./js2e (Windows: escript .\js2e), that has now been created in your current working directory.

Usage

Run ./js2e for usage instructions.

Note: The js2e tool only tries to resolve references for the file(s) you pass it. So if you need to generate Elm code from more than one file you have to pass it the enclosing directory of the relevant JSON schema files, in order for it to be able to resolve the references correctly.

A proper description of which properties are mandatory and how the generator works is still in progress, but feel free to take a look at the examples folder which contains an example of a pair of JSON schemas and their corresponding Elm output. Likewise, representations of each of the different JSON schema types are described in the lib/types folder.

Example

If we supply js2e with the following JSON schema file, definitions.json:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Definitions",
    "$id": "http://example.com/definitions.json",
    "description": "Schema for common types",
    "definitions": {
        "color": {
            "$id": "#color",
            "type": "string",
            "enum": [ "red", "yellow", "green", "blue" ]
        },
        "point": {
            "$id": "#point",
            "type": "object",
            "properties": {
                "x": {
                    "type": "number"
                },
                "y": {
                    "type": "number"
                }
            },
            "required": [ "x", "y" ]
        }
    }
}

it produces the following Elm file, Data/Definitions.elm:

module Data.Definitions exposing
    ( Color(..)
    , Point
    , colorDecoder
    , encodeColor
    , encodePoint
    , pointDecoder
    )

-- Schema for common types

import Helper.Encode as Encode
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Extra as Decode
import Json.Decode.Pipeline
    exposing
        ( custom
        , optional
        , required
        )
import Json.Encode as Encode exposing (Value)


type Color
    = Red
    | Yellow
    | Green
    | Blue


type alias Point =
    { x : Float
    , y : Float
    }


colorDecoder : Decoder Color
colorDecoder =
    Decode.string |> Decode.andThen (parseColor >> Decode.fromResult)


parseColor : String -> Result String Color
parseColor color =
    case color of
        "red" ->
            Ok Red

        "yellow" ->
            Ok Yellow

        "green" ->
            Ok Green

        "blue" ->
            Ok Blue

        _ ->
            Err <| "Unknown color type: " ++ color


pointDecoder : Decoder Point
pointDecoder =
    Decode.succeed Point
        |> required "x" Decode.float
        |> required "y" Decode.float


encodeColor : Color -> Value
encodeColor color =
    color |> colorToString |> Encode.string


colorToString : Color -> String
colorToString color =
    case color of
        Red ->
            "red"

        Yellow ->
            "yellow"

        Green ->
            "green"

        Blue ->
            "blue"


encodePoint : Point -> Value
encodePoint point =
    []
        |> Encode.required "x" point.x Encode.float
        |> Encode.required "y" point.y Encode.float
        |> Encode.object

which contains an Elm type for the color and point definitions along with their corresponding JSON decoders and encoders.

Furthermore, if we instead supply js2e with a directory of JSON schema files that have references across files, e.g.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://example.com/circle.json",
    "title": "Circle",
    "description": "Schema for a circle shape",
    "type": "object",
    "properties": {
        "center": {
            "$ref": "http://example.com/definitions.json#point"
        },
        "radius": {
            "type": "number"
        },
        "color": {
            "$ref": "http://example.com/definitions.json#color"
        }
    },
    "required": ["center", "radius"]
}

then the corresponding Elm file, Data/Circle.elm, will import the definitions (types, encoders and decoders) from the other Elm module, Data/Definitions.elm.

module Data.Circle exposing
    ( Circle
    , circleDecoder
    , encodeCircle
    )

-- Schema for a circle shape

import Data.Definitions as Definitions
import Helper.Encode as Encode
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Extra as Decode
import Json.Decode.Pipeline
    exposing
        ( custom
        , optional
        , required
        )
import Json.Encode as Encode exposing (Value)


type alias Circle =
    { center : Definitions.Point
    , color : Maybe Definitions.Color
    , radius : Float
    }


circleDecoder : Decoder Circle
circleDecoder =
    succeed Circle
        |> required "center" Definitions.pointDecoder
        |> optional "color" (Decode.nullable Definitions.colorDecoder) Nothing
        |> required "radius" Decode.float


encodeCircle : Circle -> Value
encodeCircle circle =
    []
        |> Encode.required "center" circle.center Definitions.encodePoint
        |> Encode.optional "color" circle.color Definitions.encodeColor
        |> Encode.required "radius" circle.radius Encode.float
        |> Encode.object

Furthermore, js2e also generates test files for the generated decoders and encoders to make the generated code immediately testable. The generated test files fuzzes instances of a given Elm type and tests that encoding it as JSON and decoding it back into Elm returns the original instance of that generated Elm type. In the above case, the following test files, tests/Data/CircleTests.elm and tests/Data/DefinitionsTests.elm, are generated:

module Data.CircleTests exposing
    ( circleFuzzer
    , encodeDecodeCircleTest
    )


-- Tests: Schema for a circle shape

import Data.Circle exposing (..)
import Data.DefinitionsTests as Definitions
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import Json.Decode as Decode
import Test exposing (..)


circleFuzzer : Fuzzer Circle
circleFuzzer =
    Fuzz.map3
        Circle
        Definitions.pointFuzzer
        (Fuzz.maybe Definitions.colorFuzzer)
        Fuzz.niceFloat


encodeDecodeCircleTest : Test
encodeDecodeCircleTest =
    fuzz circleFuzzer "can encode and decode Circle object" <|
        \circle ->
            circle
                |> encodeCircle
                |> Decode.decodeValue circleDecoder
                |> Expect.equal (Ok circle)

and

module Data.DefinitionsTests exposing
    ( colorFuzzer
    , encodeDecodeColorTest
    , encodeDecodePointTest
    , pointFuzzer
    )

-- Tests: Schema for common types

import Data.Definitions exposing (..)
import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer)
import Json.Decode as Decode
import Test exposing (..)


colorFuzzer : Fuzzer Color
colorFuzzer =
    [ Red, Yellow, Green, Blue ]
        |> List.map Fuzz.constant
        |> Fuzz.oneOf


encodeDecodeColorTest : Test
encodeDecodeColorTest =
    fuzz colorFuzzer "can encode and decode Color object" <|
        \color ->
            color
                |> encodeColor
                |> Decode.decodeValue colorDecoder
                |> Expect.equal (Ok color)


pointFuzzer : Fuzzer Point
pointFuzzer =
    Fuzz.map2
        Point
        Fuzz.niceFloat
        Fuzz.niceFloat


encodeDecodePointTest : Test
encodeDecodePointTest =
    fuzz pointFuzzer "can encode and decode Point object" <|
        \point ->
            point
                |> encodePoint
                |> Decode.decodeValue pointDecoder
                |> Expect.equal (Ok point)

Finally, js2e also generates package config files, package.json and elm.json, and a .tool-versions file, making it easy to test that the generated Elm code is behaving as expected. Note that the .tool-versions file is not a file required by elm nor elm-test but instead a file used by the asdf version manager, https://github.com/asdf-vm/asdf, to install and run the correct compiler versions of node and elm specified in the .tool-versions file for a given project.

Thus, if we supply the following directory structure to js2e in the above case:

.
└── js2e_input/
    β”œβ”€β”€ definitions.json
    └── circle.json

the following new directory structure is generated:

.
└── js2e_output/
    β”œβ”€β”€ .tool-versions
    β”œβ”€β”€ package.json
    β”œβ”€β”€ elm.json
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ Data/
    β”‚   β”‚   β”œβ”€β”€ Circle.elm
    β”‚   β”‚   └── Definitions.elm
    β”‚   └── Helper/
    β”‚       └── Encode.elm
    └── tests/
        └── Data/
            β”œβ”€β”€ CircleTests.elm
            └── DefinitionsTests.elm

containing the files described above along with the needed package config files to compile and run the tests.

Error reporting

Any errors encountered by the js2e tool while parsing the JSON schema files or printing the Elm code output, is reported in an Elm-like style, e.g.

--- UNKNOWN NODE TYPE -------------------------------------- all_of_example.json

The value of "type" at '#/allOf/0/properties/description' did not match a known node type

    "type": "strink"
            ^^^^^^^^

Was expecting one of the following types

    ["null", "boolean", "object", "array", "number", "integer", "string"]

Hint: See the specification section 6.25. "Validation keywords - type"
<http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25>

or

--- UNRESOLVED REFERENCE ----------------------------------- all_of_example.json


The following reference at `#/allOf/0/color` could not be resolved

    "$ref": #/definitions/kolor
            ^^^^^^^^^^^^^^^^^^^


Hint: See the specification section 9. "Base URI and dereferencing"
<http://json-schema.org/latest/json-schema-core.html#rfc.section.9>

If you encounter an error while using js2e that does not mimic the above Elm-like style, but instead looks like an Elixir stacktrace, please report this as a bug by opening an issue and including a JSON schema example that recreates the error.

Contributing

If you feel like something is missing/wrong or if I've misinterpreted the JSON schema spec, feel free to open an issue so we can discuss a solution. Note that the JSON schema parser has been moved to the new project, https://github.com/dragonwasrobot/json_schema, so this repo only implements the Elm code generators.

Please consult CONTRIBUTING.md first before opening an issue.

More Repositories

1

learn-prolog-now-exercises

My solutions to the exercises and practical sessions of the book 'Learn Prolog Now!' by Patrick Blackburn, Johan Bos, and Kristina Striegnitz.
Prolog
282
star
2

i18n-to-elm

Generates Elm types and functions from i18n key/value JSON files
Elixir
23
star
3

chip-8

A CHIP-8 emulator written in Elm
Elm
9
star
4

json_schema

A library for parsing, inspecting and manipulating JSON Schema documents
Elixir
9
star
5

brainfuck

A brainfuck interpreter written in Elm
Elm
7
star
6

simpl-lang

A simple language created in Coq, batteries and correctness proofs included.
Coq
6
star
7

gesture-recognition

Project for inferring various touch and object gestures using the TUIO protocol, CoffeeScript and node.js
CoffeeScript
4
star
8

helm-bitbucket

A helm interface for searching Bitbucket
Emacs Lisp
3
star
9

yasnippets-coq

Coq snippets for the Yasnippet emacs mode.
3
star
10

99-problems

I got 99 problems but a Lisp ain't one
Scheme
2
star
11

adventOf-CODE_2022

25 days, 25 puzzles, 25 languages, surely I will regret this decision
Clojure
2
star
12

dragonwasrobot.github.io

My website.
SCSS
2
star
13

ex_rerun

Rerun custom mix tasks on code modification
Elixir
2
star
14

formal-moessner

A formal study of Moessner's sieve
Coq
2
star
15

formal-language

A python package containing various constructs used in formal language theory, i.e. Finite Automatas, Push-down Automatas and Turing Machines.
Python
2
star
16

sicp-exercises

This is a collection of solutions to the exercises found in 'Structure and Interpretation of Computer Programs' by Harold Abelson, Gerald Jay Sussman and Julie Sussman.
Scheme
2
star
17

get-shit-done

Script for reducing distractions and improving focus
Shell
1
star
18

game-of-life

A CoffeeScript and HTML5 canvas implementation of Conway's Game of Life.
CoffeeScript
1
star
19

b.el

A byte manipulation library
Emacs Lisp
1
star