{"id":21659580,"url":"https://github.com/oxidecomputer/typify","last_synced_at":"2025-07-22T23:38:41.976Z","repository":{"id":37854411,"uuid":"409876755","full_name":"oxidecomputer/typify","owner":"oxidecomputer","description":"compiler from JSON Schema into idiomatic Rust types","archived":false,"fork":false,"pushed_at":"2025-06-30T17:01:20.000Z","size":5310,"stargazers_count":683,"open_issues_count":75,"forks_count":88,"subscribers_count":22,"default_branch":"main","last_synced_at":"2025-07-14T00:09:46.626Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oxidecomputer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.adoc","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-09-24T07:41:25.000Z","updated_at":"2025-07-11T20:46:10.000Z","dependencies_parsed_at":"2024-02-05T03:39:25.065Z","dependency_job_id":"4cec3dc1-e44b-4c08-b72b-4015e36d5b5e","html_url":"https://github.com/oxidecomputer/typify","commit_stats":{"total_commits":244,"total_committers":11,"mean_commits":"22.181818181818183","dds":0.5081967213114754,"last_synced_commit":"8d505f920c0865ead018db360d5c20e7cb668519"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/oxidecomputer/typify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Ftypify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Ftypify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Ftypify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Ftypify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oxidecomputer","download_url":"https://codeload.github.com/oxidecomputer/typify/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxidecomputer%2Ftypify/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265228692,"owners_count":23731079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-25T09:31:16.146Z","updated_at":"2025-07-22T23:38:41.956Z","avatar_url":"https://github.com/oxidecomputer.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# Typify\n\nTypify compiles JSON Schema documents into Rust types. It can be used in one of\nseveral ways:\n\n- using the [`cargo typify`](./cargo-typify/README.md) command\n\n- via the macro `import_types!(\"types.json\")` to generate Rust types directly in\n  your program\n\n- via a builder interface to generate Rust types in `build.rs` or `xtask`\n\n- via the builder functions to generate persistent files e.g. when building\n  API bindings\n\n**If generation fails, doesn't compile or is generally lousy**: Please file an\nissue and include the JSON Schema and Rust output (if there is any). Use `cargo\ntypify` command to generate code from the command-line. It's even more helpful\nif you can articulate the output you'd ideally like to see.\n\n## JSON Schema → Rust types\n\nJSON Schema is a constraint language designed for validation. As a result, it\nis not well-suited--and is often seemingly hostile--to translation into\nconstructive type systems. It allows for expressions of arbitrary complexity\nwith an infinity of ways to articulate a given set of constraints. As such,\ntypify does its best to discern an appropriate interpretation, but it is far\nfrom perfect!\n\nTypify translates JSON Schema types in a few different ways depending on some\nbasic properties of the schema:\n\n### Built-in types\n\nIntegers, floating-point numbers, strings, etc. Those all have straightforward\nrepresentations in Rust. The only significant nuance is how to select the\nappropriate built-in type based on type attributes. For example, a JSON Schema\nmight specify a maximum and/or minimum that indicates the appropriate integral\ntype to use.\n\nString schemas that include a known `format` are represented with the\nappropriate Rust type. For example `{ \"type\": \"string\", \"format\": \"uuid\" }` is\nrepresented as a `uuid::Uuid` (which requires the `uuid` crate be included as a\ndependency).\n\n### Arrays\n\nJSON Schema arrays can turn into one of three Rust types `Vec\u003cT\u003e`, `HashSet\u003cT\u003e`,\nand tuples depending on the schema properties. An array may have a fixed length\nthat matches a fixed list of item types; this is well represented by a Rust\ntuple. The distinction between `Vec\u003cT\u003e` and `HashSet\u003cT\u003e` is only if the\nschema's `uniqueItems` field is `false` or `true` respectively.\n\n### Objects\n\nIn general, objects turn into Rust structs. If, however, the schema defines no\nproperties, Typify emits a `HashMap\u003cString, T\u003e` if the `additionalProperties`\nschema specifies `T` or a `HashMap\u003cString, serde_json::Value\u003e` otherwise.\n\nProperties of generated `struct` that are not in the `required` set are\ntypically represented as an `Option\u003cT\u003e` with the `#[serde(default)]` attribute\napplied. Non-required properties with types that already have a default value\n(such as a `Vec\u003cT\u003e`) simply get the `#[serde(default)]` attribute (so you won't\nsee e.g. `Option\u003cVec\u003cT\u003e\u003e`).\n\n#### Alternate Map types\n\nBy default, Typify uses `std::collections::HashMap` as described above.\n\nIf you prefer to use `std::collections::BTreeMap` or a map type from a crate such\nas `indexmap::IndexMap`, you can specify this by calling `with_map_type` on the\n`TypeSpaceSettings` object, and providing the full path to the type you want to\nuse. E.g. `::std::collections::BTreeMap` or `::indexmap::IndexMap`.\n\nNote that for a custom map type to work you must have `T` defined to generate\na struct as described in [Objects](#objects). If `T` is not defined, typify\nwill generate code using a `serde_json::Map\u003cString, serde_json::Value\u003e` instead.\n\nSee the documentation for `TypeSpaceSettings::with_map_type` for the\nrequirements for a map type.\n\n### OneOf\n\nThe `oneOf` construct maps to a Rust enum. Typify maps this to the various\n[serde enum types](https://serde.rs/enum-representations.html).\n\n### AllOf\n\nThe 'allOf' construct is handled by merging schemas. While most of the time,\ntypify tries to preserve and share type names, it can't always do this when\nmerging schemas. You may end up with fields replicated across type; optimizing\nthis generation is an area of active work.\n\n### AnyOf\n\nThe `anyOf` construct is much trickier. If can be close to an `enum` (`oneOf`),\nbut where no particular variant might be canonical or unique for particular\ndata. While today we (imprecisely) model these as structs with optional,\nflattened members, this is one of the weaker areas of code generation.\n\nIssues describing example schemas and desired output are welcome and helpful.\n\n### AdditionalProperties\n\nThe `additionalProperties` constraint lets a schema define what *non-specified*\nproperties are permitted. A value of `false` means that no other properties are\npermitted (this is expressed in Rust with the `#[serde(deny_unknown_fields)]`\nannotation). The absence of `additionalProperties` or a value of `true` are\nequivalent constructions that mean that any other property is permitted.\n\nWithout other properties, an object that permits additional properties will be\nrepresented as a map type. In conjunction with other properties, typify employs\na heuristic interpretation. Absent or with a value of `true`, additional\nproperties are ignored. If, however, `additionalProperties` has another value\n(i.e. a schema), the generated type will have a map field annotated with\n`#[serde(flatten)]`.\n\nNote that this is true of **any** schema value for `additionalProperties` that\nis not a boolean. This includes values that would be equivalent with regard to\nvalidation such as the schema `{}` or `{ \"not\": false }` or any of the other\ninfinity of equivalent schemas. One can therefore construct a `struct` with\nnamed properties **and** a flattened map of additional properties by using a\nvalue for `additionalProperties` that is equivalent to `true` or absent with\nregard to validation, by using some e.g. `{}`.\n\n## Rust -\u003e Schema -\u003e Rust\n\nSchemas derived from Rust types may include an extension that provides\ninformation about the original type:\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": { .. },\n  \"x-rust-type\": {\n    \"crate\": \"crate-o-types\",\n    \"version\": \"1.0.0\",\n    \"path\": \"crate_o_types::some_mod::SomeType\"\n  }\n}\n```\n\nThe extension includes the name of the crate, a Cargo-style version\nrequirements spec, and the full path (that must start with ident-converted name\nof the crate).\n\nEach of the modes of using typify allow for a list of crates and versions to be\nspecified. In this case, if the user specifies \"crate-o-types@1.0.1\" for\nexample, then typify would use its `SomeType` type rather than generating one\naccording to the schema.\n\n### Using types from other crates\n\nEach mode of using typify has a method for controlling the use of types with\n`x-rust-type` annotations. The default is to ignore them. The recommended\nmethod is to specify each crate and version you intend to use. You can\nadditionally supply the `*` version for crates (which may result in\nincompatibilities) or you can define a policy to allow the use of all \"unknown\"\ncrates (which may require that addition of dependencies for those crates).\n\nFor the CLI:\n```console\n$ cargo typify --unknown-crates allow --crate oxnet@1.0.0 ...\n```\n\nFor the builder:\n```rust\nlet mut settings = typify::TypeSpaceSettings::default();\nsettings.with_unknown_crates(typify::UnknownPolicy::Allow)\n    .with_crate(\"oxnet\", typify::CrateVers::Version(\"1.0.0\".parse().unwrap()));\n```\n\nFor the macro:\n```rust\ntypify::import_types!(\n  schema = \"schema.json\",\n  unknown_crates = Allow,\n  crates = {\n    \"oxnet\" = \"1.0.0\"\n  }\n)\n```\n\n### Version requirements\n\nThe `version` field within the `x-rust-type` extension follows the Cargo\nversion requirements specification. If the extension specifies `0.1.0` of a\ncrate and the user states that they're using `0.1.1`, then the type is used;\nconversely, if the extension specifies `0.2.2` and the user is only using\n`0.2.0` the type is not used.\n\nCrate authors may choose to adhere to greater stability than otherwise provided\nby semver. If the extension version is `\u003e=0.1.0, \u003c1.0.0` then the crate author\nis committing to the schema compatibility of the given type on all releases\nuntil `1.0.0`. It is important that crate authors populate the `version` field\nin a way that upholds type availability. For example, while `*` is a valid\nvalue, it is only conceivably valid if the type in question were available in\nthe first ever version of a crate published and never changed incompatibly in\nany subsequent version.\n\n### Type parameters\n\nThe `x-rust-type` extension may also specify type parameters:\n\n```json\n{\n  \"$defs\": {\n    \"Sprocket\": {\n      \"type\": \"object\",\n      \"properties\": { .. },\n      \"x-rust-type\": {\n        \"crate\": \"util\",\n        \"version\": \"0.1.0\",\n        \"path\": \"util::Sprocket\",\n        \"parameters\": [\n          {\n            \"$ref\": \"#/$defs/Gizmo\"\n          }\n        ]\n      }\n    },\n    \"Gizmo\": {\n      \"type\": \"object\",\n      \"properties\": { .. },\n      \"x-rust-type\": {\n        \"crate\": \"util\",\n        \"version\": \"0.1.0\",\n        \"path\": \"util::Gizmo\"\n      }\n    }\n  }\n}\n```\n\nWith the `util@0.1.0` crate specified during type generation, schemas\nreferencing `#/$defs/Sprocket` would use the (non-generated) type\n`util::Sprocket\u003cutil::Gizmo\u003e`.\n\nThe `parameters` field is an array of schemas. They may be inline schemas or\nreferenced schemas.\n\n### Including `x-rust-type` in your library\n\nThe schema for the expected value is as follows:\n\n```json\n{\n  \"description\": \"schema for the x-rust-type extension\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"crate\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-zA-Z0-9_-]+$\"\n    },\n    \"version\": {\n      \"description\": \"semver requirements per a Cargo.toml dependencies entry\",\n      \"type\": \"string\"\n    },\n    \"path\": {\n      \"type\": \"string\",\n      \"pattern\": \"^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$\"\n    },\n    \"parameters\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/Schema\"\n      }\n    }\n  },\n  \"required\": [\n    \"crate\",\n    \"path\",\n    \"version\"\n  ]\n}\n```\n\nThe `version` field expresses the stability of your type. For example, if\n`0.1.0` indicates that `0.1.1` users would be fine whereas `0.2.0` users would\nnot use the type (instead generating it). You can communicate a future\ncommitment beyond what semver implies by using the [Cargo version requirement\nsyntax](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#version-requirement-syntax).\nFor example `\u003e=0.1.0, \u003c1.0.0` says that the type will remain structurally\ncompatible from version `0.1.0` until `1.0.0`.\n\n## Formatting\n\nYou can format generated code using crates such as\n[rustfmt-wrapper](https://docs.rs/rustfmt-wrapper) and\n[prettyplease](https://docs.rs/prettyplease). This can be particularly useful\nwhen checking in code or emitting code from a `build.rs`.\n\nThe examples below show different ways to convert a `TypeSpace` to a string\n(`typespace` is a `typify::TypeSpace`).\n\n\n### `rustfmt`\n\nBest for generation of code that might be checked in alongside hand-written\ncode such as in the case of an `xtask` or stand-alone code generator (such as\n`cargo-typify`).\n\n```rust\nrustfmt_wrapper::rustfmt(typespace.to_stream().to_string())?\n```\n\n### `prettyplease`\n\nBest for `build.rs` scripts where transitive dependencies might not have\n`rustfmt` installed so should be self-contained.\n\n```rust\nprettyplease::unparse(\u0026syn::parse2::\u003csyn::File\u003e(typespace.to_stream())?)\n```\n\n### No formatting\n\nIf no human will ever see the code (and this is almost never the case).\n\n```rust\ntypespace.to_stream().to_string()\n```\n\n## WIP\n\nTypify is a work in progress. Changes that affect output will be indicated with\na breaking change to the crate version number.\n\nIn general, if you have a JSON Schema that causes Typify to fail or if the\ngenerated type isn't what you expect, please file an issue.\n\nThere are some known areas where we'd like to improve:\n\n### Complex JSON Schema types\n\nJSON schema can express a wide variety of types. Some of them are easy to model\nin Rust; others aren't. There's a lot of work to be done to handle esoteric\ntypes. Examples from users are very helpful in this regard.\n\n### Bounded numbers\n\nBounded numbers aren't very well handled. Consider, for example, the schema:\n\n```json\n{\n  \"type\": \"integer\",\n  \"minimum\": 1,\n  \"maximum\": 6\n}\n```\n\nThe resulting types won't enforce those value constraints.\n\n### Configurable dependencies\n\nA string schema with `format` set to `uuid` will result in the `uuid::Uuid`\ntype; similarly, a `format` of `date` translates to\n`chrono::naive::NaiveDate`. For users that don't want dependencies on\n`uuid` or `chrono` it would be useful for Typify to optionally represent those\nas `String` (or as some other, consumer-specified type).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Ftypify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foxidecomputer%2Ftypify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxidecomputer%2Ftypify/lists"}