Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
jason-RS
Jason builds, structures, and reuses data exactly as you expect.
β¨ Features
- Template Parameters - Define reusable templates with named parameters
- File Inclusion - Compose JSON from multiple
.jasonfiles - 1:1 Conversion - Clean mapping from .jason syntax to standard JSON
- Library-First - Designed for seamless integration into Rust projects
π Quick Start
Add jason-rs to your Cargo.toml:
[dependencies] jason-rs = "0.2.5"
Parse a Jason file:
use jason_to_json;
Jason Overview
//a variable that holds a value
project_name = "jason-rs"
//what gets exported at the top level
out {
name: "Alex",
project: "jason-rs",
money: 0,
}
Jason Templates
Dev(name, project, money) {
name: name,
project: project,
money: money,
}
// invokes the template and fills in the variables with the passed-in arguments
out Dev("alex", "jason-rs", 0)
importing
Dev.jason - A file containing the dev template
Dev(name, project, money) {
name: name,
project: project,
money: money,
}
main.jason - The top-level file being compiled
import(Dev) from "./Dev.jason"
out Dev("alex", "jason-rs", 0)
Note: this will not import the context around DEV, so variables will be ignored unless imported as well.
Including
The include operator lets you include the result of one jason file into your current one as an inline value.
*showcase.jason*
out {name: "Alex", age: 20}
main.jason
value = include "./showcase.jason" // value is {name: "Alex", age: 20}
Composite Strings in Jason
Jason supports composite strings via
name = "Alex"
age = 20
out $"name {name}, my age is {age} and my account looks like {{name: name, age: age}}"
which yields
"name Alex, my age is 20 and my account looks like {\"age\":20,\"name\":\"Alex\"}"
Basic Operations in Jason
Jason supports math in its jason expressions so
value = 3.14 * 200 + 2 - 1 /4 + 4%4 // value yeilds 629.75
It is completely valid Jason. However it is importnant to note that * and + are type sysnsitive operations and their behavior changes depending on what youβre using them with.
Type Conversions and Random Numbers
Jason offers a host of built-in functions to do things like convert types from one to another and to generate values that you may need.
Int
The int() function takes in one value and gives back an Int
value1 = int("231") // 231
value2 = int(300.24123) // 300
value3 = int(300) // 300
However, the int() function can also generate a random Int by supplying a second argument so
random_int = int(0, 300)
generates a random Int between 0 and 300
Float
The float() function takes in one value and gives back a float
value1 = float("231.231") // 231.231
value2 = float(300.24123) // 300.24123
value3 = float(300) // 300.0
However, the float() function can also generate a random Float by supplying a second argument, so
random_int = float(0, 300)
generates a random Float between 0 and 300
String
The str() function takes in one value and gives back a String
value1 = str("231.231") // "231.231"
value2 = str(300.24123) // "300.24123"
value3 = str(300) // "300"
the + operation
The + operation works as both a concatenation operation with strings, lists, and objects, but as an arithmetic plus operation against Numbers, for example.
value1 = 3 + 3 // 6
value2 = [1, 2] + [3, 4] // [1, 2, 3, 4]
value3 = {name: "Alex"} + {age: 20} // {name: "Alex", age: 20}
value4 = "π" + "π" //"π" + "π"
The * operation
The * operation works as a copy operator as well as a multiplicitive one so.
value1 = 3 * 3 // 9
value2 = "alex" * 3 // ["alex", "alex", "alex"]
value3 = int(0, 300) * 3 // note Int(min, max) gets a random int. [33, 33, 33]
Unique Jason Operators
Jason provides a selection of operators to help with transforming and manipulating data in a way that doesnβt feel too pragmatic.
The at Operator
The at operator lets you index a List or an Object at a specific index and or key.
value1 = [1,2,3,4,5] at 0 // 1
value2 = {name: "alex", age: 20} at "name" // "alex"
The at operator will let you know if you index out of bounds or index a key that doesnβt exist.
Error: Indexing Error in file ./testing.jason on line 5: invalid convert number 231 at list with len 5
5 | value1 = [1, 2, 3, 4, 5] at 231
Indexing Error in file ./testing.jason on line 6: key doesn't exit e
6 | value2 = { name : "alex", age : 20 } at "e"
The repeat Operator
The first unique and useful Jason operator is the repeat operator which is similar to the * operator where it copies a value for an Int amount of times. However, it also reevaluates said values.
values = int(0, 300) repeat 3 //yeilds [99,27,127]
The pick and upick operators
The pick operator lets you pick a Int of randomly selected elements from a list. You can pick more elements than there are, since it builds a new list from those randomly selected elements.
Weβll introduce a list nums for our examples
nums = [1,2,3]
picked_nums = nums pick 5 // yields [2,3,2,3,1]
However, note that pick operations are random, so my examples may differ from what you run.
The upick operator lets you pick a number of unique elements from a List, and what this means by unique is not quite in value but in index, so if you have a linear list of different values, you can upick 4 values from it and get 4 unique values. However, you can not upick more than the number of elements in the list since they must be of unique index.
nums = [1,2,3]
picked_nums = nums upick 3 //[1,2,3]
the map operator
If youβve ever used a language with map operations before, this operator is basically the same except it works as a binary operator between a list of values and some expression.
[1,2,3] map(n) n * 2 //yields [2,4,6]
So it iterates over each element in the list and maps it to the correct value in the expression.
The map operator also has an overload to allow you to get the index along with the value by simply.
[1,2,3] map(n, i) {value: n, index: i}
//yields [{"index":0,"value":1},{"index":1,"value":2},{"index":2,"value":3}]
Debuging in jason via info and infoT
The info operator lets prints out the type and value of an expression or variable along with any other relevant info.
info [30, 20.124, "hello"]
The info it produces: (This does not appear in the compiled JSON)
β[Info] Token at line 4, col 5. in file: ./testing.jason
β Code:
β
β 4 | info [30, 20.124, "hello"]
β
β Value: [30,20.124,"hello"]
β Type: [Float | String | Int]
ββββββββββββββ
You can also do a similar thing to test jason types.
infoT >= 0 while < 100 | Null | {name: String, age: >= 0}'
The infoT produces (This also does not appear in the compiled JSON)
β[Type-Info] Token at line 4, col 6. in file: ./testing.jason
β Code:
β
β 4 | infoT >= 0 while < 100 | Null | { name : String, age : >= 0 } '
β
β Type: [0,100) | Null | {age: [0,Infinity), name: String}'
ββββββββββββββ
Basic Types
Typing in Jason is completely optional; however, if you do plan to use them, jason has an algebra with a few pretty simple operators to allow easy composable types.
Jason includes a small but useful collection of base types to choose from that accurately reflect all types of json that may appear in day-to-day problems.
- Generic and Null types
Any- Allows any values of alltypes.Null- Allows the valuenull.
- Numeric Types
Number- Includes all numeric values.Int- Includes all non-floating point values.Float- Includes all numbers but expects it in the format of afloatI.E., 2.0, not 2.
- String Type
String- permitsStringvalues I.E. "paul", "dave"
- Bool Type
Bool- permits Boolean values I.E.true,false.
examples:
value1: Int = 3 // β
value2: Float = 3 // β
value3: Float = 3.0 // β
value4: Bool = true // β
value5: String = "Hello World :)" // β
value6: Any = 213.231 // β
value7: Null = null // β
a sample error message:
Error: Type Error in file ./testing.jason on line 2: type mismatches
expected Float, found Int
2 | value2 : Float = 3
^^^^^^
Type Constraints
While it feels that jasonβs base types are pretty constraining (get it), you can extend the complexity of types via type constraints.
Union Operator |
The union operator allows you to check against multiple types for one value.
value1: Int | Null = 3 // β
value1 = null // β
value1 = 3.0 // β
Value Constraints
You can also ensure that the value is the specific value you have in mind, and you can do this for String, Float, Bool and Int types.
value1: 302 | 3.14 | "paul" = 302 // β
value1 = 3.14 // β
value1 = "paul" // β
value1 = 2 // β
You can also constrain values to a range, like so
pos_num: >= 0 = 3 // β
pos_num = 0 // β
pos_num = -1 // β
You can also do ranges with >, <, >=, and <=, and if you want to combine intervals, you simply use the while operator, so
value: > 0 while < 20 = 4
yields the interval of (0, 20).
Note Constraints via ranges get reinterpreted as intervals by the compiler and get presented as such via errors. For example:
Error: Type Error in file ./testing.jason on line 4: type mismatches
expected [0,Infinity), found Int
4 | pos_num = -1
^^^^^^^
And with this, this stops impossible intervals from occurring, like in the case of
num: > 0 while < 0 = 2
with an error like
Error: Value Error in file ./testing.jason on line 1: Empty interval: bounds meet at 0 but not both inclusive
1 | num : > 0 while < 0 = 2
List Types
Types for Lists are pretty simple, where you can express them as such.
values: [T] = [...]
nums: [Number] = [20, 14.231, 40] // β
You can also express any possible type as a List, so
ages: [>= 0 | "unknown"] = [20, 30, 40, "unknown"] // β
ages = [20, 30, 40, "unknown", true] // β
with an example error
Error: Type Error in file ./testing.jason on line 1: type mismatches
expected [[0,Infinity) | "unknown"], found [Bool | Int | String]
1 | ages : [ >= 0 | "unknown"] = [20, 30, 40, "unknown", true]
^^^^
Type syntax
As a note, you can separate type definitions from value assignment since from this point on types are gonna get bigger, so a few operators to mention are
The :: Operator
Allows you to define a type so
Age :: 0 >= while < 130
creates a type Age where you can use anywhere else, so the general form is.
NewType :: T
Tempalte Types
To define a type for a template you need to use the :: operator and with this you supply the name of the template Tempalte(Type1, Type2) :: ResultType in this format where each type in the parathenses relates to a parameter.
exmaple:
Person :: {
name: String,
age: >= 0
}
Citizen(String, >= 0) :: Person
Citizen(name, age) {
name: name,
age: age
}
out Citizen("alex", 20)
this outputs
{"age":20,"name":"alex"}
however if we made the age negative we get
Error: Type Error in file ./testing.jason on line 14: expected type [0,Infinity) for age found Int in template Citizen
14 | out Citizen("alex", -20)
because it doesn't match our parameter type for age and if our age didn't require >= 0 then
Error: Type Error in file ./testing.jason on line 14: Template Citizen resulted in {age: Int, name: String} expected {age: [0,Infinity), name: String}
Type mismatches:
~ age: expected [0,Infinity), found Int
14 | out Citizen("alex", -20)
Our result type would catch it.
Object Types
Object Types in Jason not only validate that the values are gonna be what you expect, but also that the structure is preserved after compile time, so you donβt have to worry about unexpected labels added to add typing and structure to your data.
Object Types function pretty similarly to how the List types look, so you slot in the type where youβd expect the value to be at each key, so the general representation would be
NewType :: {
key1: Type1,
key2: Type2,
}
You can also nest object types so
NewType :: {
key1: Type1,
key2: {
InnerKey1: InnerType1,
InnerKey2: InnerType2,
...
},
...
}
It is also completely valid.
A more concrete example would be
Person :: {
name: String,
age: >= 0
}
person: Person = {name: "Alex", age: 20}
and we can also see that if we try to give it a value that isnβt in our type definition of Person, like in the case of
person: Person = {name: "Alex", age: -1}
We get a detailed breakdown of whatβs wrong with our object types here.
Error: Type Error in file ./testing.jason on line 6: type mismatches
expected {age: [0,Infinity), name: String}, found {age: Int, name: String}
Type mismatches:
~ age: expected [0,Infinity), found Int
6 | person : Person = { name : "Alex", age : -1 }
^^^^^^
Object Type Operators +, &, ', with
This is a pretty big jumble of operators for just Object types, but I feel they bring a pretty good edge to dealing with a variety of possible Object Types you may need in a way where you donβt have to manually write type data.
The & Operator
The & operator allows you to combine fields of type objects, and if fields are shared across two objects, they union together.
Examples:
Person :: {age: >= 0, language: String, name: String, social_media: {}}
PhotoBookUser :: {age: "privated", email: String, social_media: {photobook_url: String}}
Person & PhotoBookUser resolves into
{
age: [0,Infinity) | "privated",
email: String,
language: String,
name: String,
social_media: {
photobook_url: String
}
}
The + operator
The + operator functions similarly to the & operator, except itβs right-dominant (so values on the right take precedence over values on the left), and it doesnβt affect heavily nested fields, so if we refer to our other example, and we use Person + PhotoBookUser, it resolves into
{
age: "privated",
email: String,
language: String,
name: String,
social_media: {
photobook_url: String
}
}
The ' Operator
The ' operator is probably the simplest operator in this series of operators, where the only thing it requires is that you have at least one field of the desired Object type.
person: Person' = {age: 20} // β
person = {name: "Alex"} // β
person = {} // β
The βwithβ Operator
The with operator allows you to fill in an Object's type so if we take our Person object, we can do something like
NulledPerson :: Person with Null
and the type of NulledPerson will look like
{
age: Null,
language: Null,
name: Null,
social_media: {}
}
And this may not seem as purposeful as the other operators, however, combining this with the original Person Type can net some pretty cool.
NullablePerson :: Person & (Person with Null)
In the above example, NullablePerson will result in the type.
{
age: [0,Infinity) | Null,
language: String | Null,
name: String | Null,
social_media: {}
}
and this makes your Person type full Nullable without having to write any extra type information!
JasonBuilder
JasonBuilder allows you to add Lua dependencies to your .jason parsing pipeline.
Start with no Lua dependencies:
use JasonBuilder;
let builder = new;
Adding Lua Dependencies
Include Lua files:
let builder = new
.include_lua_file?
.include_lua_file?;
Or raw Lua source:
let lua_code = r#"function add(a,b) return a+b end"#;
let builder = new.include_lua?;
Both methods are chainable, allowing you to add multiple Lua dependencies easily.
Then you can just run the standard functions for converting jason to json using builder.
result:
{
"email": "jasondev@gmail.com",
"ip": 23123,
"password": "user.passxp",
"username": "jasondev"
}
Errors
Error outputs are nice and concise and propagate nicely.
Example error
Error: Lua Function Error in file ./main.jason on line 8: failed to find function random_name: error converting Lua nil to function
8 | out Some(Person(random_name()!, random_int(30)!)) * 50
^^^^^^^^^^^^^^
License
Licensed under the Apache License 2.0. See LICENSE for details.