Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / vue.js

A Superset of JSON for Data Modeling

4.58/5 (4 votes)
1 May 2021CPOL2 min read 7.4K  
A superset of JSON with improved type inference capability
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:

JavaScript
{
  "users": [
    {
      "name": "Peter",
      "email": "hello@peter.com"
    },
    {
      "name": "Tom",
      "email": "hello@tom.com"
    }
  ]
}

We can clearly infer the type information (as a C# class):

C#
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:

JavaScript
{
  "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:

C#
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:

C#
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:

C#
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:

JavaScript
{
  "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:

C#
class User
{
  public string Name { get; set; }
  public string Email { get; set; }
  public List<User> Friends { get; set; }
}

but this would be equally possible:

C#
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.)

JavaScript
{
  "users": [
    {
      "name": "Peter",
      "email": "hello@peter.com",
      "tasks": [
        //manyToMany
        {
          "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:

JavaScript
{
  "users": [
    {
      "userName": "Peter",
      "email": "hello@peter.com",
      "friends": [
        //class:user
        //manyToMany
        {
          "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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)