Schemas for JSON optimized for code generation

JSON Type Definition, aka RFC 8927, is an easy-to-learn, portable, and standardized way to describe the shape of your JSON data.

user.jtd.json
{
  "properties": {
    "id": { "type": "string" },
    "createdAt": { "type": "timestamp" },
    "karma": { "type": "int32" },
    "isAdmin": { "type": "boolean" }
  }
}
export interface User {
  createdAt: string;
  id: string;
  isAdmin: boolean;
  karma: number;
}
package user

import "time"

type User struct {
	CreatedAt time.Time `json:"created_at"`
	ID        string    `json:"id"`
	IsAdmin   bool      `json:"isAdmin"`
	Karma     int32     `json:"karma"`
}
package com.example;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.OffsetDateTime;

@JsonSerialize
public class User {
    @JsonProperty("createdAt")
    private OffsetDateTime createdAt;

    @JsonProperty("id")
    private String id;

    @JsonProperty("isAdmin")
    private Boolean isAdmin;

    @JsonProperty("karma")
    private Integer karma;

    public User() {
    }

    public OffsetDateTime getCreatedAt() {
        return this.createdAt;
    }

    public void setCreatedAt(OffsetDateTime createdAt) {
        this.createdAt = createdAt;
    }

    public String getId() {
        return this.id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Boolean getIsAdmin() {
        return this.isAdmin;
    }

    public void setIsAdmin(Boolean isAdmin) {
        this.isAdmin = isAdmin;
    }

    public Integer getKarma() {
        return this.karma;
    }

    public void setKarma(Integer karma) {
        this.karma = karma;
    }
}
using System;
using System.Text.Json.Serialization;

namespace Contoso.Example
{
    public class User
    {
        [JsonPropertyName("createdAt")]
        public DateTimeOffset CreatedAt { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("isAdmin")]
        public bool IsAdmin { get; set; }

        [JsonPropertyName("karma")]
        public int Karma { get; set; }
    }
}
from dataclasses import dataclass
from typing import Any, Optional, Union, get_args, get_origin

@dataclass
class User:
    created_at: 'str'
    id: 'str'
    is_admin: 'bool'
    karma: 'int'

    @classmethod
    def from_json(cls, data) -> "User":
        """
        Construct an instance of this class from parsed JSON data.
        """

        return cls(
            _from_json(str, data.get("createdAt")),
            _from_json(str, data.get("id")),
            _from_json(bool, data.get("isAdmin")),
            _from_json(int, data.get("karma")),
        )

    def to_json(self):
        """
        Generate JSON-ready data from an instance of this class.
        """

        out = {}
        out["createdAt"] = _to_json(self.created_at)
        out["id"] = _to_json(self.id)
        out["isAdmin"] = _to_json(self.is_admin)
        out["karma"] = _to_json(self.karma)
        return out

def _from_json(cls, data):
    if data is None or cls in [bool, int, float, str, object] or cls is Any:
        return data
    if get_origin(cls) is Union:
        return _from_json(get_args(cls)[0], data)
    if get_origin(cls) is list:
        return [_from_json(get_args(cls)[0], d) for d in data]
    if get_origin(cls) is dict:
        return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() }
    return cls.from_json(data)

def _to_json(data):
    if data is None or type(data) in [bool, int, float, str, object]:
        return data
    if type(data) is list:
        return [_to_json(d) for d in data]
    if type(data) is dict:
        return { k: _to_json(v) for k, v in data.items() }
    return data.to_json()
# You can optionally generate .rbs too! (scroll to botton)

require 'json'
require 'time'

module Example
  class User
    attr_accessor :created_at
    attr_accessor :id
    attr_accessor :is_admin
    attr_accessor :karma

    def self.from_json_data(data)
      out = User.new
      out.created_at = Example::from_json_data(DateTime, data["createdAt"])
      out.id = Example::from_json_data(String, data["id"])
      out.is_admin = Example::from_json_data(TrueClass, data["isAdmin"])
      out.karma = Example::from_json_data(Integer, data["karma"])
      out
    end

    def to_json_data
      data = {}
      data["createdAt"] = Example::to_json_data(created_at)
      data["id"] = Example::to_json_data(id)
      data["isAdmin"] = Example::to_json_data(is_admin)
      data["karma"] = Example::to_json_data(karma)
      data
    end
  end

  private

  def self.from_json_data(type, data)
    if data.nil? || [Object, TrueClass, Integer, Float, String].include?(type)
      data
    elsif type == DateTime
      DateTime.rfc3339(data)
    elsif type.is_a?(Array)
      data.map { |elem| from_json_data(type.first, elem) }
    elsif type.is_a?(Hash)
      data.transform_values { |elem| from_json_data(type.values.first, elem) }
    else
      type.from_json_data(data)
    end
  end

  def self.to_json_data(data)
    if data.nil? || [TrueClass, FalseClass, Integer, Float, String].include?(data.class)
      data
    elsif data.is_a?(DateTime)
      data.rfc3339
    elsif data.is_a?(Array)
      data.map { |elem| to_json_data(elem) }
    elsif data.is_a?(Hash)
      data.transform_values { |elem| to_json_data(elem) }
    else
      data.to_json_data
    end
  end
end

# If you enable it, you'll also get a .rbs file:

module Example
  class User
    attr_accessor created_at: DateTime
    attr_accessor id: String
    attr_accessor is_admin: bool
    attr_accessor karma: Integer

    def self.from_json_data: (untyped) -> User
    def to_json_data: () -> untyped
  end

  def self.from_json_data: (untyped, untyped) -> untyped
  def self.to_json_data: (untyped) -> untyped
end
use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct User {
    #[serde(rename = "createdAt")]
    pub createdAt: DateTime<FixedOffset>,

    #[serde(rename = "id")]
    pub id: String,

    #[serde(rename = "isAdmin")]
    pub isAdmin: bool,

    #[serde(rename = "karma")]
    pub karma: i32,
}

Use JSON Typedef with the language of your choice

Why JSON Typedef?

A better way to work with JSON

JSON Type Definition is a lightweight schema language for JSON data. Describe the shape of your data once, and get portable validators and types across many languages.

Tiny and Easy to Learn

Most developers can learn the entire JSON Typedef specification in about five to ten minutes.

Portable and Specific Errors

Specific validation errors are part of the JTD spec. Every implementation returns the same errors.

Powerful Code Generation

The jtd-codegen tool can consistently generate code in many programming languages from any schema.

Easy Onboarding

Want to add JSON Typedef to an existing system? jtd-infer can generate a schema from examples of your data.

Mocking and Fuzzing

Once you have a schema, jtd-fuzz can generate mocks of your data. Generate seed data or load-testing workloads with ease.

Easy to Embed

JSON Typedef schemas are just plain old JSON, so you can embed them in your organization's JSON (or YAML) custom tools.

Ready to be more productive with JSON? Dig into the docs.