Skip to content

Commit d1d0c35

Browse files
Adds JSON encoders for all JSON schema types. (#30)
* Adds a JSON encoder for 'array' types * Adds JSON encoders for 'enum' types * Adds JSON encoders for 'object' types * Adds JSON encoders for 'oneOf' types * Adds JSON encoders for 'union' types * Adds generic 'print_encoder' to printer.ex * Removes 'Decoders' from the output paths * Adjusts all type descriptions and tests accordingly * Now 'imports' Util functions in printers * Updates example Elm output * Bumps major version (v2.0) because of breaking changes in JS2E.generate
1 parent dec024f commit d1d0c35

30 files changed

+1160
-265
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# The packaged executable
2+
js2e
3+
14
# The directory Mix will write compiled artifacts to.
25
/_build
36

README.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# JSON schema to Elm
22

3-
Generates Elm types and JSON decoders from JSON schema specifications.
3+
Generates Elm types, JSON decoders and JSON encoders from JSON schema
4+
specifications.
45

56
## Installation
67

@@ -9,16 +10,12 @@ and its build tool `mix` installed, this can be done with `brew install elixir`
910
or similar.
1011

1112
- Clone this repository: `git clone [email protected]:dragonwasrobot/json-schema-to-elm.git`
12-
- Compile the project: `MIX_ENV=prod mix deps.get && mix compile`
13-
- Build an executable: `MIX_ENV=prod mix escript.build`
13+
- Build an executable: `MIX_ENV=prod mix build`
1414
- An executable, `js2e`, has now been created in your current working directory.
1515

16-
For further help, look up the documentation on how
17-
to [install escripts](https://hexdocs.pm/mix/Mix.Tasks.Escript.Install.html).
18-
1916
## Usage
2017

21-
See `./js2e` for usage instructions.
18+
Run `./js2e` for usage instructions.
2219

2320
A proper description of which properties are mandatory are how the generator
2421
works is still in progress, but feel free to take a look at the `examples`

config/test.exs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
use Mix.Config
2+
3+
config :logger, level: :debug
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
module Domain.Circle exposing (..)
2+
3+
-- Schema for a circle shape
4+
5+
import Json.Decode as Decode
6+
exposing
7+
( float
8+
, int
9+
, string
10+
, list
11+
, succeed
12+
, fail
13+
, map
14+
, maybe
15+
, field
16+
, at
17+
, andThen
18+
, oneOf
19+
, nullable
20+
, Decoder
21+
)
22+
import Json.Decode.Pipeline
23+
exposing
24+
( decode
25+
, required
26+
, optional
27+
, custom
28+
)
29+
import Json.Encode as Encode
30+
exposing
31+
( Value
32+
, float
33+
, int
34+
, string
35+
, list
36+
, object
37+
)
38+
import Domain.Definitions
39+
exposing
40+
( Color
41+
, colorDecoder
42+
, encodeColor
43+
, Point
44+
, pointDecoder
45+
, encodePoint
46+
)
47+
48+
49+
type alias Root =
50+
{ center : Point
51+
, color : Maybe Color
52+
, radius : Float
53+
}
54+
55+
56+
rootDecoder : Decoder Root
57+
rootDecoder =
58+
decode Root
59+
|> required "center" pointDecoder
60+
|> optional "color" (Decode.string |> andThen colorDecoder |> maybe) Nothing
61+
|> required "radius" Decode.float
62+
63+
64+
encodeRoot : Root -> Value
65+
encodeRoot root =
66+
let
67+
center =
68+
[ ( "center", encodePoint root.center ) ]
69+
70+
color =
71+
case root.color of
72+
Just color ->
73+
[ ( "color", encodeColor color ) ]
74+
75+
Nothing ->
76+
[]
77+
78+
radius =
79+
[ ( "radius", Encode.float root.radius ) ]
80+
in
81+
object <|
82+
center
83+
++ color
84+
++ radius

examples/example-output-elm-code/Domain/Decoders/Circle.elm

-48
This file was deleted.

examples/example-output-elm-code/Domain/Decoders/Definitions.elm renamed to examples/example-output-elm-code/Domain/Definitions.elm

+46-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
module Domain.Decoders.Definitions exposing (..)
1+
module Domain.Definitions exposing (..)
22

33
-- Schema for common types
44

55
import Json.Decode as Decode
66
exposing
7-
( int
7+
( float
8+
, int
89
, string
10+
, list
911
, succeed
1012
, fail
11-
, list
1213
, map
14+
, maybe
1315
, field
1416
, at
1517
, andThen
@@ -24,6 +26,15 @@ import Json.Decode.Pipeline
2426
, optional
2527
, custom
2628
)
29+
import Json.Encode as Encode
30+
exposing
31+
( Value
32+
, float
33+
, int
34+
, string
35+
, list
36+
, object
37+
)
2738

2839

2940
type Color
@@ -61,5 +72,35 @@ colorDecoder color =
6172
pointDecoder : Decoder Point
6273
pointDecoder =
6374
decode Point
64-
|> required "x" float
65-
|> required "y" float
75+
|> required "x" Decode.float
76+
|> required "y" Decode.float
77+
78+
79+
encodeColor : Color -> Value
80+
encodeColor color =
81+
case color of
82+
Red ->
83+
Encode.string "red"
84+
85+
Yellow ->
86+
Encode.string "yellow"
87+
88+
Green ->
89+
Encode.string "green"
90+
91+
Blue ->
92+
Encode.string "blue"
93+
94+
95+
encodePoint : Point -> Value
96+
encodePoint point =
97+
let
98+
x =
99+
[ ( "x", Encode.float point.x ) ]
100+
101+
y =
102+
[ ( "y", Encode.float point.y ) ]
103+
in
104+
object <|
105+
x
106+
++ y

lib/js2e.ex

+2-3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ defmodule JS2E do
9797
end
9898

9999
output_path
100-
|> Path.join("Decoders")
101100
|> File.mkdir_p!()
102101

103102
output_path
@@ -107,9 +106,9 @@ defmodule JS2E do
107106
def generate(json_schema_paths, module_name) do
108107

109108
schema_dict = Parser.parse_schema_files(json_schema_paths)
110-
printed_decoders = Printer.print_schemas(schema_dict, module_name)
109+
printed_schemas = Printer.print_schemas(schema_dict, module_name)
111110

112-
printed_decoders
111+
printed_schemas
113112
|> Enum.each(fn{file_path, file_content} ->
114113
{:ok, file} = File.open file_path, [:write]
115114
IO.binwrite file, file_content

lib/printer.ex

+39-8
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ defmodule JS2E.Printer do
2020
title = schema_def.title
2121

2222
if module_name != "" do
23-
"./#{module_name}/Decoders/#{title}.elm"
23+
"./#{module_name}/#{title}.elm"
2424
else
25-
"./Decoders/#{title}.elm"
25+
"./#{title}.elm"
2626
end
2727
end
2828

@@ -59,12 +59,17 @@ defmodule JS2E.Printer do
5959
decoders = values
6060
|> Enum.map_join("\n\n", &(print_decoder(&1, type_dict, schema_dict)))
6161

62+
encoders = values
63+
|> Enum.map_join("\n\n", &(print_encoder(&1, type_dict, schema_dict)))
64+
6265
"""
6366
#{String.trim(preamble)}
6467
\n
6568
#{String.trim(types)}
6669
\n
6770
#{String.trim(decoders)}
71+
\n
72+
#{String.trim(encoders)}
6873
"""
6974
end
7075

@@ -133,12 +138,31 @@ defmodule JS2E.Printer do
133138
end
134139
end
135140

136-
@spec determine_schema_id(URI.t) :: String.t
137-
defp determine_schema_id(identifier) do
138-
identifier
139-
|> URI.parse
140-
|> URI.merge("#")
141-
|> to_string
141+
@spec print_encoder(
142+
Types.typeDefinition,
143+
Types.typeDictionary,
144+
Types.schemaDictionary
145+
) :: String.t
146+
def print_encoder(type_def, type_dict, schema_dict) do
147+
148+
type_to_printer_dict = %{
149+
"ArrayType" => &ArrayPrinter.print_encoder/3,
150+
"EnumType" => &EnumPrinter.print_encoder/3,
151+
"ObjectType" => &ObjectPrinter.print_encoder/3,
152+
"PrimitiveType" => &PrimitivePrinter.print_encoder/3,
153+
"OneOfType" => &OneOfPrinter.print_encoder/3,
154+
"UnionType" => &UnionPrinter.print_encoder/3,
155+
"TypeReference" => &TypeReferencePrinter.print_encoder/3
156+
}
157+
158+
struct_name = Util.get_string_name(type_def)
159+
160+
if Map.has_key?(type_to_printer_dict, struct_name) do
161+
printer = type_to_printer_dict[struct_name]
162+
printer.(type_def, type_dict, schema_dict)
163+
else
164+
Logger.error "Error(print_encoder) unknown type: #{inspect struct_name}"
165+
end
142166
end
143167

144168
@spec resolve_type(
@@ -176,7 +200,14 @@ defmodule JS2E.Printer do
176200
else
177201
type
178202
end
203+
end
179204

205+
@spec determine_schema_id(URI.t) :: String.t
206+
defp determine_schema_id(identifier) do
207+
identifier
208+
|> URI.parse
209+
|> URI.merge("#")
210+
|> to_string
180211
end
181212

182213
end

0 commit comments

Comments
 (0)