Generate Model schemas from JSON and JSON Schemas in 20 languages using quicktype.io

Sometimes you just want to create Model objects around some type of JSON payload of schema definition.  It seems like you either end up building your own tool, doing the conversion manually, or hunting around to find some schema generator.  Sometimes there just isn't a tool that you want to use and sometimes there is.

I'm working Dart where we want to create immutable objects from deserialized JSON schema for AdaptiveCards.  I ran across an open-source tool, quicktype.io, that can generate JSON serializable model classes for over 20 different languages. There are several different settings that let do some customization.  You can then consume those classes/functions as is or tune them to your needs. It is a great learning aid even if you don't use it for your models.  I really like how it lets me compare how different languages handle models and JSON.

Disclaimer:  I have no idea who created https://quicktype.io or how it is run.  Pull down their GitHub repo and scan it for malware if you don't trust other websites with your JSON or JSON schema and need to run it locally.

Pick a language

The quicktype.io language selector. JSON serialization and deserialization are supported across all languages.  Immutable and functional model objects are supported in some subset of the languages. My only experience is some exploration with Dart.


Generator Options for Various Languages

Programming languages have their on requirements and capabilities.  The language generators each have their own feature settings.

Video

Creating an Immutable Serializable Dart Class from JSON

The Dart generator gives us an immutable, serializable Model object that looks like
import 'package:meta/meta.dart';
import 'dart:convert';


///A geographical coordinate
class Coordinate {
    final double latitude;
    final double longitude;

    Coordinate({
        required this.latitude,
        required this.longitude,
    });

    Coordinate copyWith({
        double? latitude,
        double? longitude,
    }) =>
        Coordinate(
            latitude: latitude ?? this.latitude,
            longitude: longitude ?? this.longitude,
        );

    factory Coordinate.fromRawJson(String str) => Coordinate.fromJson(json.decode(str));

    String toRawJson() => json.encode(toJson());

    factory Coordinate.fromJson(Map<String, dynamic> json) => Coordinate(
        latitude: json["latitude"]?.toDouble(),
        longitude: json["longitude"]?.toDouble(),
    );

    Map<String, dynamic> toJson() => {
        "latitude": latitude,
        "longitude": longitude,
    };
}

Python

The Python generator created 
from dataclasses import dataclass
from typing import Optional, Any, TypeVar, Type, cast


T = TypeVar("T")


def from_float(x: Any) -> float:
    assert isinstance(x, (float, int)) and not isinstance(x, bool)
    return float(x)


def from_none(x: Any) -> Any:
    assert x is None
    return x


def from_union(fs, x):
    for f in fs:
        try:
            return f(x)
        except:
            pass
    assert False


def to_float(x: Any) -> float:
    assert isinstance(x, float)
    return x


def to_class(c: Type[T], x: Any) -> dict:
    assert isinstance(x, c)
    return cast(Any, x).to_dict()


@dataclass
class Coordinate:
    """A geographical coordinate"""

    latitude: Optional[float] = None
    longitude: Optional[float] = None

    @staticmethod
    def from_dict(obj: Any) -> "Coordinate":
        assert isinstance(obj, dict)
        latitude = from_union([from_float, from_none], obj.get("latitude"))
        longitude = from_union([from_float, from_none], obj.get("longitude"))
        return Coordinate(latitude, longitude)

    def to_dict(self) -> dict:
        result: dict = {}
        if self.latitude is not None:
            result["latitude"] = from_union([to_float, from_none], self.latitude)
        if self.longitude is not None:
            result["longitude"] = from_union([to_float, from_none], self.longitude)
        return result


def coordinate_from_dict(s: Any) -> Coordinate:
    return Coordinate.from_dict(s)


def coordinate_to_dict(x: Coordinate) -> Any:
    return to_class(Coordinate, x)


How good is it?

The generators vary in completeness, capabilities and, how well they track language standards. IMO it is a good tool to see how pieces fit together even if you manually write your own classes or your own generator.

Some of the converters may be out of date with respect to the latest version of the language. You can always submit a pull request to bring it up to date. That is one of the beauties of OpenSource.

Revision History

Created 2023/09


Comments

Popular posts from this blog

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Almost PaaS Document Parsing with Tika and AWS Elastic Beanstalk