id |
---|
json-schema-mappings |
This is a documentation of the mapping strategy from Morphir types to Json Schema.
This document describes how Morphir Models maps to Json Schema.
Json Schema Reference can be found here
Additional reading:
We will give a quick overview of the mapping in the table below:
Type | Elm sample | JSON sample | Comment |
---|---|---|---|
Bool |
True , False |
true , false |
Exact mapping |
Int |
123 , -15 |
123 , -15 |
Ints map to JSON number |
Float |
3.14 , -53.2 |
3.14 , -53.2 |
Floats map to JSON number |
Char |
'A' , 'z' |
"A" , "z" |
Chars map to JSON strings |
String |
"Foo bar" , "" |
"Foo bar" , "" |
Exact mapping |
Maybe a |
Just 13 , Nothing |
13 , null |
Maybe maps to nullable JSON value |
List a |
[1, 2, 3] , [] |
[1, 2, 3] , [] |
Lists map to JSON arrays |
tuples |
( 13, False ) |
[13, false] |
Tuples map to arrays |
record types |
{ foo = 13, bar = False } |
{ "foo": 13, "bar": false } |
Records map to objects |
custom types |
FooBar "hello , MyEnum |
["FooBar", "hello"] , "MyEnum" |
see details below |
unit |
() |
{} |
Follow the two step s below to generate a Json Schema
- Step 1
Run the
elm morphir-elm make
command to generate an IR - Step 2
Run the
elm morphir-elm gen -t JsonSchema
to generate the Json Schema
Note - The generated schema is named <package-name>.json
by default. But you can specify the filename
optionally for the schema using the -f flag.
Next, we will get into some specific cases that may need further explanation.
The rest of the explains how each Morphir type maps to the Json Schema Types.
-
1.1.1. Bool
1.1.2. Int
1.1.3. Float
1.1.4. Char
1.1.5. String \1.2.1. Decimal
1.2.2. LocalDate
1.2.3. LocalTime
1.2.4. Month\ -
2.3.1. General Case
2.3.2. Special Cases
- No-arg Constructor
- Single Constructor \
Basic types
Boolean type in Morphir maps to boolean in Json Schema as shown in the example:
type Overtime
= Boolean
Json Schema
"Overtime" : {
"type" : "boolean"
}
which owould validate against
true
Decimal, Ints and Float types in Morphir all map to number
type in Json Schema. An example is given below
Morphir model:
type Age
= Int
type Amount
= Decimal
Json Schema
"Age" : {
"type" : "number"
}
"Amount" : {
"type" : "number"
}
Float types in Morphir all map to number type in Json Schema. An example is given below
type Score
= Float
would generate the following schema:
"Score" : {
"type" : "number"
}
Will validate against:
45.5
Char
types in Morphir directly maps to string
type in Json Schema since
Json Schema does not have a native Char type
Therefore elm model:
type Grade
= Char
would map to the following schema:
"Grade" : {
"type" : "string"
}
Will validate against:
"A"
String
types in Morphir directly map to string
type in Json Schema.
Therefore, elm model:
type LastName
= String
would map to the following schema:
"Lastname" : {
"type" : "string"
}
Will validate against:
"foo"
Decimal values are would be mapped to string in the JSON schema. The elm pattern
property is
used to specified the precision.
LocalDate types in Morphir are mapped to strings in Json Schema. The format attribute in the JSON schema is used to provide the format for the date.
LocalDate types in Morphir are mapped to strings in Json Schema. The format attribute in the JSON schema is used to provide the format for the time.
Month types in Morphir are mapped OneOf schema type with a enum list of all the month names
A Maybe type in Morphir refers to a value that may not exist. This means that it could either be a value or a null. There are two approaches to handling Maybes.\
- Set the value of the type to an array of two strings: the type, and "null" \
- Treat a Maybe as a custom type with two constructors: the type and null Here, we adopt the second approach. Therefore, the model \
type alias Manager =
Maybe String
would map to the schema
"Types.Manager": {
"oneOf": [
{
"type": "null"
},
{
"type": "string"
}
]
}
An array is list of items. Json schema specification supports array validation:
- List Validation - each item in the array matches the same schema
- Tuple Validation - each item in the array may have a different schema Union types in Morphir maps to arrays in Json Schema. Details are given below in the Custom Types sections.
The array schema contains the items
keyword. For list validation, this keyword is set to a single schema that would be
used to validate all items in the array
For tuple validation, when we want disallows extra items in the tuple, the 'items' keyword is set to false.
This is a keyword that specifies an array used to hold the schemas for a tuple-validated array. Each item in prefixItems is a schema that corresponds to each index of the document's array
A set is used to define a collection of unique values. A Json Schema can ensure that
each of the items in the array is unique. To achieve this, we map a set to an array and set the uniqueItems
keyword to true.
Since we have an approach for mapping Tuples, a Morphir Dict can be considered as a list of Tuples. However, the challenge would be to enforce the unique key constraint. So when we have the Morphir declaration
type alias acronyms =
Dict String String
Can be represented as list of list, like so:
[[String, String]]
This is expected to validate the following Json document
[["eg", "example"], ["ie", "that is"]]
The first item in each list represents the key in the dictionary and right now, we don't have a way to ensure uniqueness of this item
A Result in Elm represents the result of a computation that may fail. Therefore, it can be considered as a custom type with two constructors: Ok and Err. With this approach, we can map a result the same way we map Custom types.
The following declaration
type alias Output
= Result String Int
would be equivalent to the following json schema
{
"oneOf": [
{
"type": "array",
"items": false,
"prefixItems": [
{
"const": "Err"
},
{
"type": "string"
}
],
"minItems": 2,
"maxItems": 2
},
{
"type": "array",
"items": false,
"prefixItems": [
{
"const": "Ok"
},
{
"type": "integer"
}
],
"minItems": 2,
"maxItems": 2
}
]
}
Composite types are types composed of other types. The following composite types are covered below: Tuples, Records, Custom Types
Since tuples represents a list of possibly different types, a Morphir tuple could
be mapped to a tuple-validated array.
For example elm (String, Int)
would result in:
{
"type": "array",
"items": false,
"prifixitems" : [
{
"type": "string"
},
{
"type": "integer"
}
],
"minItems": 2,
"maxItems": 2
}
Record types in Morphir maps to objects in Json schema. The fields of the record maps to properties of the Json Schema object. The properties of a JSON schema is a list of schemas. The only clarification we need to make is that field names use a camel case naming convention
Example 1
sample1 =
{ fooBar = "hello"
, fooBaz = 13
}
Which maps to the following JSON:
{
"fooBar" : "hello",
"fooBaz" : 13
}
Example 2
type alias Address =
{ country : String
, state : String
, street : String
}
Equivalent Json schema
"Records.Address": {
"type": "object",
"properties": {
"Country": {
"type": "string"
},
"State": {
"type": "string"
},
"Street": {
"type": "string"
}
}
}
This validates against the json document:
{
"Country" : "United States",
"State" : "Texas",
"Street" : "25 Rain drive"
}
Custom types in Morphir, are union types with one or more items in the union. These items are called tags or constructors. Each item has zero or more arguments which are types. Json Schema does not support custom types natively. So we use the following approach.
- a constructor with its arguments will map to a tuple-validated array where the constructor is the first item in the array
- the schema type for the constructor would be const (explained below)
- the union type itself would then be represented using the "anyOf" keyword
Custom types are special union types where each subtype is marked with a special tag to make it easier to differentiate. Besides the tag each subtype can also have any number of arguments. These tags are also called constructors since you can think of them as functions with different names and arguments that create instances of the same type. Here's an example:
type Foo
= FooBar String
| FooBaz Int Bool
sample1 =
FooBar "hello"
sample2 =
FooBaz 13 False
Our JSON format needs to capture both the tag and the arguments and also connect them together. So we decided to simply put all of them in an array starting with the tag as the first value:
["FooBar", "hello"]
["FooBaz", 13, false]
For the tags we use upper camel case (which is also called PascalCase).
The following Morphir model:
type Person
= Adult String
| Child String Int
would map to the schema:
"Person.Child": {
"type": "array",
"items": false,
"prefixItems": [
{
"const": "Child"
},
{
"type": "string"
},
{
"type": "integer"
}
]
}
The resulting "anyOf" schema would be as shown below:
"Person": {
"anyOf": [
{
"const": "Child"
},
{
"const": "Person"
}
]
}
No-arg Constructor
As mentioned previously, when a constructor in a union type has zero arguments, then it maps to a const
schema
The model:
type Currency
= USD
would generate the schema:
"Currency" : {
"const" : "USD"
}
**- Single Constructor \ ** When a constructor doesn't have any arguments it behaves like an enum value. The format described above would dictate that we map those to single element arrays in JSON but for simplicity we will map them to just a string value:
sample3 =
MyEnumValue
Maps to:
"MyEnumValue"
When a schema specifies a type of null, it has only one acceptable value: null. In Json, null is equivalent to a value being absent.
A schema can reference another schema using the $ref keyword. The valueof the $ref is the URI-reference that is resolved against the base URI. For reference types in Morphir, the $ref keyword is used to refer to the target schema. In the example below, the Address field of the Bank schema refers to the Address schema. So the $ref keyword is used:
Morphir model:
type alias Bank =
{ bankName : String
, address : Address}
type alias Address =
{ country : String
, state : String
, street : String
}
Json Schema:
"Records.Bank": {
"type": "object",
"properties": {
"Address": {
"$ref": "#/defs/Address"
},
"BankName": {
"type": "string"
}
}
}
The anyOf keyword is used to relate a schema with it's subschemas.