JSON is an excellent tool to serialize data, it is human readable, easy to understand. It is not possible however, to completely infer type information from a piece of JSON code. I propose a superset of JSON that addresses this problem.
Type Inference Problems with JSON
Some JSON contains their type information completely, for example, let's look at the following piece of code:
{
"users": [
{
"name": "Peter",
"email": "hello@peter.com"
},
{
"name": "Tom",
"email": "hello@tom.com"
}
]
}
We can clearly infer the type information (as a C# class):
class User
{
public string Name { get; set; }
public string Email { get; set; }
}
One-To-Many or Many-To-Many?
However, if we complicate it just a little bit, type information becomes ambigious:
{
"users": [
{
"name": "Peter",
"email": "hello@peter.com",
"tasks": [
{
"title": "Shopping",
"description": "Buy: milk, bread, apples"
}
]
},
{
"name": "Tom",
"email": "hello@tom.com",
"tasks": []
}
]
}
Now we can still infer the user
class:
class User
{
public string Name { get; set; }
public string Email { get; set; }
public List<Task> Tasks { get; set; }
}
But to infer the task
class, we would need to now what the relation is between the user
and the task
class. Is it one-to-many or is it many-to-may? Can a task only have one assignee or multiple assignees?
One possibility would be in the one-to-many case:
class Task
{
public string Title { get; set; }
public string Description { get; set; }
public User Assignee { get; set; }
}
The other in the many-to-many case:
class Task
{
public string Title { get; set; }
public string Description { get; set; }
public List<User> Assignees { get; set; }
}
Same Class in Multiple Places
Another problem with infering types from JSON code is when the same class appears in multiple places in the same piece of code. For example:
{
"users": [
{
"userName": "Peter",
"email": "hello@peter.com",
"friends": [
{
"userName": "Tom",
"email": "hello@tom.com"
}
]
}
]
}
In this code, it is not clear whether friends
of users
are also users
, or are they instances of a different friend
class.
Most people would agree with the following interpretation:
class User
{
public string Name { get; set; }
public string Email { get; set; }
public List<User> Friends { get; set; }
}
but this would be equally possible:
class User
{
public string Name { get; set; }
public string Email { get; set; }
public List<Friend> Friends { get; set; }
}
class Friend
{
public string Name { get; set; }
public string Email { get; set; }
}
JSON Annotations
Comments
Comments are not allowed in standard JSON, however it is common for JSON parser libraries to support this feature. Some libraries that support comments: Json.NET, JSON5, JsonCpp.
Because of their widespread support, comments are a good way to implement annotations for JSON. Also, if you would like to write documentation (or an article like this) which includes annotated JSON code, syntax highlighting is generally available.
Many-To-Many Annotation
Returning to our second example, we can clarify the relation between users and tasks, by adding a manyToMany
annotation. (Let's say that a task can have multiple assignees.)
{
"users": [
{
"name": "Peter",
"email": "hello@peter.com",
"tasks": [
{
"title": "Shopping",
"description": "Buy: milk, bread, apples"
}
]
},
{
"name": "Tom",
"email": "hello@tom.com",
"tasks": []
}
]
}
Please note that the comment is inside the task collection, but not inside the task
object. Also note, that only one of the task collections needs to have the annotation. Both line comments, and block comments are supported, so //manyToMany
and /*manyToMany*/
are equivalent.
Class Name Annotation
We can also clarify the users
/ friends
example with an annotation that states the class name for friends
:
{
"users": [
{
"userName": "Peter",
"email": "hello@peter.com",
"friends": [
{
"userName": "Tom",
"email": "hello@tom.com"
}
]
}
]
}
Please note that the collection is called "users
" in plural and the class is called "user
" in singular.
What is this Good for?
This simple language can describe a set of data with its data model in one place in a very compact form. It is possible to create code generators that converts these annotated JSON files into:
- Swagger (Open API Specification) files
- Database initialization scripts in SQL
- Or even complete applications. An example for this is https://bootgen.com/ that can create ASP.NET 5 - Vue.js application from annotated JSON.
Reference Implementation
You can find a reference implementation of this language on GitHub. This library can infer the data model for an annotated JSON file, and generate code for this data model using Scriban templates.
History
- 27th April, 2021: First version
- 1st May, 2021: Block comment support