JSON Type Definition, aka RFC 8927, is an easy-to-learn, portable, and standardized way to describe the shape of your JSON data.
{
"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,
}
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.
Most developers can learn the entire JSON Typedef specification in about five to ten minutes.
Specific validation errors are part of the JTD spec. Every implementation returns the same errors.
The jtd-codegen
tool can
consistently generate code in
many programming
languages from any schema.
Want to add JSON Typedef to an existing system?
jtd-infer
can generate a schema from examples of your data.
Once you have a schema,
jtd-fuzz
can generate mocks of your data. Generate seed data or
load-testing workloads with ease.
JSON Typedef schemas are just plain old JSON, so you can embed them in your organization's JSON (or YAML) custom tools.