使用quicktype从json模式生成类型

本文介绍了如何使用quicktype库从JSON模式生成TypeScript和Python的类型定义,通过配置、编写脚本和运行发电机,简化了处理JSON数据时的类型转换工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Heading image

Building on from previous posts on a spike on JSON Schema, we will continue in this one by looking at an alternative library to the json-schema-to-typescript the library previously explored.

在以前的JSON Schema上的文章的基础上,我们将继续研究以前探索过的json-schema-to-typescript库的替代库。

配置 (Setting Up)

# From a yarn initialised project
yarn add quicktype-core
# setting up the files
touch index.js book.json

For book.json, add the following. It will follow a similar JSON schema we've used previously with the Book but a few changes, so are sure to copy-paste it across.

对于book.json ,添加以下内容。 它将遵循我们之前在Book中使用过的类似JSON模式,但需要进行一些更改,因此请确保将其复制粘贴。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "author": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "preferredName": { "type": "string" },
        "age": { "type": "number" },
        "gender": { "enum": ["male", "female", "other"] }
      },
      "required": ["name", "preferredName", "age", "gender"]
    },
    "title": { "type": "string" },
    "publisher": { "type": "string" }
  },
  "required": ["author", "title", "publisher"]
}

写出脚本(Writing out the script)

index.js will look like the following:

index.js将如下所示:

const {
  quicktype,
  InputData,
  JSONSchemaInput,
  JSONSchemaStore,
} = require("quicktype-core")
const path = require("path")
const fs = require("fs")


async function quicktypeJSONSchema(targetLanguage, typeName, jsonSchemaString) {
  const schemaInput = new JSONSchemaInput(new JSONSchemaStore())


  // We could add multiple schemas for multiple types,
  // but here we're just making one type from JSON schema.
  await schemaInput.addSource({ name: typeName, schema: jsonSchemaString })


  const inputData = new InputData()
  inputData.addInput(schemaInput)


  return await quicktype({
    inputData,
    lang: targetLanguage,
  })
}


async function main() {
  // read the schema details
  const schemaFilepath = path.join(__dirname, "bookWithoutUser.json")
  const bookSchema = fs.readFileSync(schemaFilepath, "utf-8")


  const { lines: tsPerson } = await quicktypeJSONSchema(
    "typescript",
    "Book",
    bookSchema
  )
  console.log(tsPerson.join("\n"))


  const { lines: pythonPerson } = await quicktypeJSONSchema(
    "python",
    "Book",
    bookSchema
  )
  console.log(pythonPerson.join("\n"))
}


main()

In the above script, we are going to generate TypeScript and Python output for the sake of demonstration.

在上面的脚本中,为了进行演示,我们将生成TypeScriptPython输出。

运行发电机 (Running the generator)

Run node index.js and you will get the following output for TypeScript and Python respectively:

运行node index.js ,您将分别为TypeScript和Python获得以下输出:

// To parse this data:
//
//   import { Convert, Book } from "./file";
//
//   const book = Convert.toBook(json);
//
// These functions will throw an error if the JSON doesn't
// match the expected interface, even if the JSON is valid.


export interface Book {
  author: Author
  publisher: string
  title: string
}


export interface Author {
  age: number
  gender: Gender
  name: string
  preferredName: string
}


export enum Gender {
  Female = "female",
  Male = "male",
  Other = "other",
}


// Converts JSON strings to/from your types
// and asserts the results of JSON.parse at runtime
export class Convert {
  public static toBook(json: string): Book {
    return cast(JSON.parse(json), r("Book"))
  }


  public static bookToJson(value: Book): string {
    return JSON.stringify(uncast(value, r("Book")), null, 2)
  }
}


function invalidValue(typ: any, val: any, key: any = ""): never {
  if (key) {
    throw Error(
      `Invalid value for key "${key}". Expected type ${JSON.stringify(
        typ
      )} but got ${JSON.stringify(val)}`
    )
  }
  throw Error(
    `Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`
  )
}


function jsonToJSProps(typ: any): any {
  if (typ.jsonToJS === undefined) {
    const map: any = {}
    typ.props.forEach((p: any) => (map[p.json] = { key: p.js, typ: p.typ }))
    typ.jsonToJS = map
  }
  return typ.jsonToJS
}


function jsToJSONProps(typ: any): any {
  if (typ.jsToJSON === undefined) {
    const map: any = {}
    typ.props.forEach((p: any) => (map[p.js] = { key: p.json, typ: p.typ }))
    typ.jsToJSON = map
  }
  return typ.jsToJSON
}


function transform(val: any, typ: any, getProps: any, key: any = ""): any {
  function transformPrimitive(typ: string, val: any): any {
    if (typeof typ === typeof val) return val
    return invalidValue(typ, val, key)
  }


  function transformUnion(typs: any[], val: any): any {
    // val must validate against one typ in typs
    const l = typs.length
    for (let i = 0; i < l; i++) {
      const typ = typs[i]
      try {
        return transform(val, typ, getProps)
      } catch (_) {}
    }
    return invalidValue(typs, val)
  }


  function transformEnum(cases: string[], val: any): any {
    if (cases.indexOf(val) !== -1) return val
    return invalidValue(cases, val)
  }


  function transformArray(typ: any, val: any): any {
    // val must be an array with no invalid elements
    if (!Array.isArray(val)) return invalidValue("array", val)
    return val.map(el => transform(el, typ, getProps))
  }


  function transformDate(val: any): any {
    if (val === null) {
      return null
    }
    const d = new Date(val)
    if (isNaN(d.valueOf())) {
      return invalidValue("Date", val)
    }
    return d
  }


  function transformObject(
    props: { [k: string]: any },
    additional: any,
    val: any
  ): any {
    if (val === null || typeof val !== "object" || Array.isArray(val)) {
      return invalidValue("object", val)
    }
    const result: any = {}
    Object.getOwnPropertyNames(props).forEach(key => {
      const prop = props[key]
      const v = Object.prototype.hasOwnProperty.call(val, key)
        ? val[key]
        : undefined
      result[prop.key] = transform(v, prop.typ, getProps, prop.key)
    })
    Object.getOwnPropertyNames(val).forEach(key => {
      if (!Object.prototype.hasOwnProperty.call(props, key)) {
        result[key] = transform(val[key], additional, getProps, key)
      }
    })
    return result
  }


  if (typ === "any") return val
  if (typ === null) {
    if (val === null) return val
    return invalidValue(typ, val)
  }
  if (typ === false) return invalidValue(typ, val)
  while (typeof typ === "object" && typ.ref !== undefined) {
    typ = typeMap[typ.ref]
  }
  if (Array.isArray(typ)) return transformEnum(typ, val)
  if (typeof typ === "object") {
    return typ.hasOwnProperty("unionMembers")
      ? transformUnion(typ.unionMembers, val)
      : typ.hasOwnProperty("arrayItems")
      ? transformArray(typ.arrayItems, val)
      : typ.hasOwnProperty("props")
      ? transformObject(getProps(typ), typ.additional, val)
      : invalidValue(typ, val)
  }
  // Numbers can be parsed by Date but shouldn't be.
  if (typ === Date && typeof val !== "number") return transformDate(val)
  return transformPrimitive(typ, val)
}


function cast<T>(val: any, typ: any): T {
  return transform(val, typ, jsonToJSProps)
}


function uncast<T>(val: T, typ: any): any {
  return transform(val, typ, jsToJSONProps)
}


function a(typ: any) {
  return { arrayItems: typ }
}


function u(...typs: any[]) {
  return { unionMembers: typs }
}


function o(props: any[], additional: any) {
  return { props, additional }
}


function m(additional: any) {
  return { props: [], additional }
}


function r(name: string) {
  return { ref: name }
}


const typeMap: any = {
  Book: o(
    [
      { json: "author", js: "author", typ: r("Author") },
      { json: "publisher", js: "publisher", typ: "" },
      { json: "title", js: "title", typ: "" },
    ],
    "any"
  ),
  Author: o(
    [
      { json: "age", js: "age", typ: 3.14 },
      { json: "gender", js: "gender", typ: r("Gender") },
      { json: "name", js: "name", typ: "" },
      { json: "preferredName", js: "preferredName", typ: "" },
    ],
    "any"
  ),
  Gender: ["female", "male", "other"],
}

The Python output:

Python输出:

# To use this code, make sure you
#
#     import json
#
# and then, to convert JSON from a string, do
#
#     result = book_from_dict(json.loads(json_string))


from enum import Enum
from typing import Any, TypeVar, Type, cast




T = TypeVar("T")
EnumT = TypeVar("EnumT", bound=Enum)




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




def from_str(x: Any) -> str:
    assert isinstance(x, str)
    return x




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




def to_enum(c: Type[EnumT], x: Any) -> EnumT:
    assert isinstance(x, c)
    return x.value




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




class Gender(Enum):
    FEMALE = "female"
    MALE = "male"
    OTHER = "other"




class Author:
    age: float
    gender: Gender
    name: str
    preferred_name: str


    def __init__(self, age: float, gender: Gender, name: str, preferred_name: str) -> None:
        self.age = age
        self.gender = gender
        self.name = name
        self.preferred_name = preferred_name


    @staticmethod
    def from_dict(obj: Any) -> 'Author':
        assert isinstance(obj, dict)
        age = from_float(obj.get("age"))
        gender = Gender(obj.get("gender"))
        name = from_str(obj.get("name"))
        preferred_name = from_str(obj.get("preferredName"))
        return Author(age, gender, name, preferred_name)


    def to_dict(self) -> dict:
        result: dict = {}
        result["age"] = to_float(self.age)
        result["gender"] = to_enum(Gender, self.gender)
        result["name"] = from_str(self.name)
        result["preferredName"] = from_str(self.preferred_name)
        return result




class Book:
    author: Author
    publisher: str
    title: str


    def __init__(self, author: Author, publisher: str, title: str) -> None:
        self.author = author
        self.publisher = publisher
        self.title = title


    @staticmethod
    def from_dict(obj: Any) -> 'Book':
        assert isinstance(obj, dict)
        author = Author.from_dict(obj.get("author"))
        publisher = from_str(obj.get("publisher"))
        title = from_str(obj.get("title"))
        return Book(author, publisher, title)


    def to_dict(self) -> dict:
        result: dict = {}
        result["author"] = to_class(Author, self.author)
        result["publisher"] = from_str(self.publisher)
        result["title"] = from_str(self.title)
        return result




def book_from_dict(s: Any) -> Book:
    return Book.from_dict(s)




def book_to_dict(x: Book) -> Any:
    return to_class(Book, x)

Hooray! We can cut a lot of fluff with these helpers.

万岁! 使用这些辅助工具可以减少很多绒毛。

资源和进一步阅读 (Resources And Further Reading)

  1. QuickType

    快速类型

Image credit: Alessio Rinella

图片来源: Alessio Rinella

Originally posted on my blog.

最初发布在我的博客上

翻译自: https://medium.com/@dennisokeeffe/generating-types-from-json-schema-with-quicktype-a9d96ea1f2bf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值