Library / SDK
GREsau/schemars avatar
GREsau/schemars

Schemars: Generate JSON Schema from Rust Types

Generate JSON Schema documents from Rust code

1,422 stars310 forksRustMIT

At a glance

What is it?
Schemars derives JSON Schema directly from Rust types and respects applicable Serde attributes. It suits Rust APIs that need schemas alongside serializable request and response models, but example-based generation is less precise for complex enums.
Who is it for?
Rust developers should adopt Schemars when schemas must stay close to Serde-based serialization and ordinary Rust types can implement JsonSchema. Projects depending on highly dynamic serialization, incomplete enums, or types that cannot provide a representative example are poorer candidates.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why Schemars Fits Rust API Type Systems

Schemars addresses a specific Rust problem: API schemas tend to drift when a JSON contract is maintained separately from the structs that implement that contract. Adding a field to a Rust request type can silently invalidate a separately written schema, while field names, optionality, and enum representation can change during serialization. Schemars moves schema generation next to the Rust type definition so the two can be reviewed together.

The intended users are developers building Rust APIs, configuration interfaces, or other systems that serialize Rust values as JSON. The central requirement is a Rust type from which the schema can be derived. The README states that every field of a derived type must also implement JsonSchema, although Schemars supplies implementations for many standard library types. This makes derive-based generation more useful for conventional structs and enums than for arbitrary dynamic data.

Schemars is not a general JSON validator or a replacement for Serde. Serde defines how values are serialized and deserialized, while Schemars describes the expected JSON shape. Its stated aim is compatibility with Serde, so the useful adoption test is whether the schema should describe the same representation that serde_json will actually produce and accept.

JsonSchema Derivation and Recursive Schema Output

The main mechanism consists of the JsonSchema derive and the schema_for! macro. Applying JsonSchema to a type allows Schemars to inspect that type when schema generation is requested. The schema_for! macro then creates a schema for the selected root type, including reusable definitions for referenced types.

In the README's basic example, MyStruct contains an integer, a boolean, and an Option<MyEnum>. The generated document identifies the root as an object and places its properties under properties. Required non-optional fields appear in required, while Option<MyEnum> is represented as an anyOf containing a reference to MyEnum and a null type. MyEnum is moved into $defs because the enum needs its own definition and is referenced from the root property.

This recursive structure is useful because it follows Rust references rather than flattening every nested type into one unwieldy object. Standard library types such as i32, bool, Option, Vec, and f32 appear in the example output as integer, boolean, nullable, array, and number schemas with format information where applicable.

The result is serialized through serde_json::to_string_pretty. The crate therefore produces a schema value that can be rendered as JSON for publication, testing, API documentation, or storage. The repository layout separates the runtime crate into schemars and the procedural implementation into schemars_derive, which matches the division between schema representation and derive processing.

Serde Attributes in Schemars Derives

Schemars reads applicable #[serde(...)] attributes on types that derive JsonSchema. According to the README, this is intended to keep generated schemas aligned with Serde behavior. The documented example combines Deserialize, Serialize, and JsonSchema, then changes the serialized field names and object policy through Serde attributes.

With #[serde(rename_all = "camelCase")], Rust fields such as my_int and my_bool become myInt and myBool in the generated schema. A field-level #[serde(rename = "myNumber")] controls one property name directly. #[serde(deny_unknown_fields)] results in additionalProperties being set to false. A field marked #[serde(default)] gains a default entry in its property schema, while it remains absent from required.

The enum example uses #[serde(untagged)], and the generated schema uses anyOf rather than oneOf. This distinction follows from the Serde representation rather than merely from the Rust enum declaration. Such details are where derive-based schema generation can save maintenance effort, because the JSON contract follows the serializer configuration already attached to the type.

Schemars also provides #[schemars(...)] attributes that behave like the corresponding Serde attributes for schema generation. This creates a deliberate separation point. A project can use #[schemars(rename_all = "camelCase")] when the schema needs different names from the serialized JSON, or it can use Schemars without Serde. The README does not describe automatic conflict detection beyond stating that Schemars attributes can override Serde attributes, so projects using both systems should review those overrides explicitly.

Adding Schemars and Printing the First Schema

The README links to the schemars crate on Crates.io but does not provide an installation command or a dependency declaration. Based on the repository's Cargo workspace, the relevant published package is schemars, and the implementation is divided between the schemars and schemars_derive workspace members. A reader should obtain the package from the linked Crates.io listing and add it according to the version policy of the surrounding Rust project.

The first concrete use requires the imports, derives, root type, and invocation shown below. The code serializes the root type, so the surrounding project also needs access to serde_json for this exact example.

rust
use schemars::{schema_for, JsonSchema};

#[derive(JsonSchema)]
pub struct MyStruct {
    pub my_int: i32,
    pub my_bool: bool,
    pub my_nullable_enum: Option<MyEnum>,
}

#[derive(JsonSchema)]
pub enum MyEnum {
    StringNewType(String),
    StructVariant { floats: Vec<f32> },
}

let schema = schema_for!(MyStruct);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());

The printed document should declare https://json-schema.org/draft/2020-12/schema, describe MyStruct as an object, and place the two non-optional fields in required. The nullable enum should use anyOf with a reference to MyEnum and a null branch. The exact output can differ when Serde or Schemars attributes change the representation, which is why comparing the document with the intended wire format is the important first check.

Example Values and Their Precision Cost

Schemars can also generate a schema from a concrete value through schema_for_value!, provided that value's type implements serde::Serialize. This offers a fallback when a type cannot or does not implement JsonSchema, but the README is direct about the trade-off: schemas inferred from one value are generally less precise.

Enum representation is the clearest limitation. With schema_for_value!, the documented example supplies only MyEnum::StringNewType("foo"). The resulting schema records the observed string form in examples and cannot establish the complete set of representations accepted by every enum variant. The output shown by the README is based on the older draft-07 URI and contains less detailed type information than the derived output.

The following example shows the documented fallback mechanism:

rust
use schemars::schema_for_value;
use serde::Serialize;

#[derive(Serialize)]
pub struct MyStruct {
    pub my_int: i32,
    pub my_bool: bool,
    pub my_nullable_enum: Option<MyEnum>,
}

let schema = schema_for_value!(MyStruct {
    my_int: 123,
    my_bool: true,
    my_nullable_enum: Some(MyEnum::StringNewType("foo".to_string()))
});

This approach is appropriate for a small, stable shape when adding a derive is impractical. It is the wrong choice when exhaustive enum constraints, required fields, validation boundaries, or multiple accepted representations must be communicated. In those cases, implementing JsonSchema gives the generator type information rather than asking it to generalize from a sample.

Compatibility Boundaries and Manual Schema Work

Schemars does not guarantee that arbitrary Serde implementations map perfectly to JSON Schema. Its compatibility target is strong by design, but the actual result depends on what the type expresses and on which serialization attributes can be interpreted. Dynamic values, custom Serialize implementations, and shapes without a faithful static representation can still require project-specific schema annotations or manual schema logic.

The distinction between Schema from Example Value and Schema for a Rust Type is a real alternative within Schemars itself. The first path accepts a Serialize value and describes the observed structure. The second uses JsonSchema and can express fields, optionality, variants, definitions, and Serde attributes. A manual JSON Schema document is another approach when the external contract intentionally differs from the Rust representation, although it reintroduces the drift problem that Schemars is designed to reduce.

Schemars also generates JSON Schema documents, not OpenAPI or AsyncAPI specifications. The README does not document wrappers for either format. Consumers that require an OpenAPI document must therefore determine how the generated schema is embedded into that larger format.

MSRV, Releases, and MIT Upgrade Boundaries

The repository uses semantic versioning, with documented qualifications. An increase in MSRV, or Minimum Supported Rust Version, is treated as a semver-minor change. The project aims to support the past year of stable Rust releases, but the README explicitly says this is not guaranteed. The published project badge states an MSRV of 1.74 or later.

Release history provides current version points rather than a maintenance guarantee by itself. The listed releases are v1.2.1 on 2026-02-01, v1.2.2 on 2026-07-27, and v1.2.3 on 2026-10-12. The last push was also on 2026-10-12. The repository is not archived, but adoption decisions should still account for the project's stated MSRV policy and the possibility of a minor-version MSRV increase.

Schemars is distributed under the MIT license. That permits use, modification, distribution, and sublicensing under the license's conditions, subject to its notice requirements. This is not legal advice, and downstream projects still need to comply with the repository's LICENSE file. Upgrade review should focus on generated schema diffs, Serde or Schemars attribute interactions, and the minimum Rust version accepted by the consuming build.

Editorial conclusion

Rust developers should adopt Schemars when schemas must stay close to Serde-based serialization and ordinary Rust types can implement JsonSchema. Projects depending on highly dynamic serialization, incomplete enums, or types that cannot provide a representative example are poorer candidates. Check the generated schema against Serde behavior first, especially for renamed fields, defaults, untagged enums, and unknown-field handling, then decide whether its precision and draft output satisfy the API contract.

Frequently asked questions

How do I generate a JSON Schema from a Rust type with Schemars?

Derive JsonSchema on the type and call schema_for! with the root type. Every field must also implement JsonSchema, although Schemars provides implementations for many standard library types.

Does Schemars respect Serde attributes?

Yes. Schemars checks applicable #[serde(...)] attributes and adjusts the generated schema. Its documented override mechanism is #[schemars(...)], which can change the schema without changing Serde behavior.

What is the difference between schema_for! and schema_for_value!?

schema_for! derives a schema from a type that implements JsonSchema. schema_for_value! works from a Serialize value when JsonSchema is unavailable, but the README says the resulting schema is generally less precise, especially for enums.

What Rust version does Schemars require?

The project badge states MSRV 1.74+. The README also says Schemars aims to support the past year of stable Rust versions, but does not guarantee that policy and treats an MSRV increase as a semver-minor change.

Official sources

  1. GREsau/schemars on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gresau-schemars.svg)](https://hysenlabs.com/projects/gresau-schemars)

Was this useful?