Search
Search documentation
Working with JSON Data in Rust
Updated Oct 28, 2026
JSON data validation and schema operations using JSON Schema standards โ from Rust.
Available Functions
The data API provides utilities for working with JSON data (non-tabular):
load_data- Load a JSON payload from a resource (inline or file)validate_data- Validate a JSON payload against a Data Schemainfer_data_schema- Automatically infer a Data Schema from JSON datavalidate_data_schema- Validate the Data Schema itselfrender_data_schema_as- Render a Data Schema through the render plugins
A "Data Schema" is a JSON Schema (Draft 2020-12 compatible) describing arbitrary structured JSON โ distinct from a Table Schema, which describes columnar tabular data. In Rust, DataSchema is a serde_json::Map.
validate_data_schema and load_data_schema live in fairspec::fairspec_metadata; the rest in fairspec.
Loading Data
Load a JSON payload from a resource. The payload can be inline (a literal JSON value stored on the resource) or fetched from a local file or remote URL:
use fairspec::load_data;
use serde_json::json;
// Load from a local file
let resource = serde_json::from_value(json!({"data": "data.json"}))?;
let data = load_data(&resource)?;
// Load from a remote URL
let resource = serde_json::from_value(json!({"data": "https://example.com/data.json"}))?;
let data = load_data(&resource)?;
// Load inline data (descriptor already contains the payload)
let resource = serde_json::from_value(json!({
"data": {"users": [{"id": 1, "name": "Alice"}]},
}))?;
let data = load_data(&resource)?;load_data returns Option<serde_json::Value> โ typically an object, array, or primitive, depending on the JSON content. The result is None if the source cannot be resolved.
Validating Data
Validate a JSON payload against the dataSchema attached to the resource:
use fairspec::validate_data;
use serde_json::json;
let resource = serde_json::from_value(json!({
"data": "user.json",
"dataSchema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0},
},
"required": ["name", "email"],
},
}))?;
let report = validate_data(&resource)?;
if !report.valid {
for error in &report.errors {
println!("{}", serde_json::to_value(error)?["message"]);
}
}The returned Report has valid: bool and errors: Vec<FairspecError>. A data error carries a message and a jsonPointer to the failing value. Messages are jsonschema-rs wording, so they differ from the Python and TypeScript libraries.
The Data Schema is checked first: a $schema other than a Fairspec Data Schema profile or https://json-schema.org/draft/2020-12/schema, and a $ref or $dynamicRef that does not resolve within the schema, are metadata errors under /dataSchema, and no data is checked. A remote reference is never fetched. format is asserted for date, time, date-time, duration, email, ipv4 and uuid; any other format is an annotation.
Example error, serialised:
{
"type": "data",
"message": "-1 is less than the minimum of 0",
"jsonPointer": "/users/0/age"
}Inferring a Data Schema
Automatically generate a JSON Schema from a JSON payload:
use fairspec::infer_data_schema;
use serde_json::json;
let resource = serde_json::from_value(json!({"data": "users.json"}))?;
let schema = infer_data_schema(&resource)?;The inference detects:
- Data types (
string,number,integer,boolean,null) - Object structures and nested properties
- Array items and their types
- Required properties based on presence
- Enum values for low-cardinality fields
The result is an Option<JsonSchema>, a JSON object usable directly as a dataSchema.
Example โ given users.json:
[
{ "id": 1, "name": "Alice", "email": "alice@example.com", "age": 30, "active": true },
{ "id": 2, "name": "Bob", "email": "bob@example.com", "age": 25, "active": false }
]The inferred schema is:
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string" },
"age": { "type": "integer" },
"active": { "type": "boolean" }
},
"required": ["id", "name", "email", "age", "active"]
}
}Validating a Data Schema
Validate that a Data Schema itself is well-formed (valid JSON Schema):
use fairspec::fairspec_metadata::validate_data_schema;
let result = validate_data_schema("schema.json", None)?;
if result.valid {
println!("Schema is valid");
} else {
for error in &result.errors {
println!("{}", serde_json::to_value(error)?["message"]);
}
}validate_data_schema accepts either a descriptor path/URL or a literal Descriptor. The check confirms the schema is:
- Valid JSON
- Compliant with JSON Schema Draft 2020-12
- Using valid keywords and formats
To validate only a sub-schema inside a larger descriptor, pass a root JSON pointer:
use fairspec::fairspec_metadata::validate_data_schema;
let result = validate_data_schema("dataset.json", Some("/resources/0/dataSchema"))?;Rendering a Data Schema
render_data_schema_as asks the render plugins for the requested format:
use fairspec::fairspec_metadata::{RenderDataSchemaOptions, load_data_schema};
use fairspec::render_data_schema_as;
let schema = load_data_schema("schema.json")?;
let markdown = render_data_schema_as(&schema, &RenderDataSchemaOptions {
format: "markdown".into(),
..Default::default()
})?;No Rust plugin renders yet, so the result is None for every format.
Working with DataSchema
DataSchema is a JSON object. You can construct one literally or load it from a file:
use fairspec::fairspec_metadata::{DataSchema, load_data_schema};
use serde_json::json;
// From a literal
let schema: DataSchema = serde_json::from_value(json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"host": {"type": "string"},
"port": {"type": "integer", "minimum": 1, "maximum": 65535},
"ssl": {"type": "boolean"},
},
"required": ["host", "port"],
}))?;
// From a file
let schema = load_data_schema("config-schema.json")?;Common Workflows
Create and Validate with a Schema
use fairspec::{infer_data_schema, validate_data};
use serde_json::json;
// Infer schema from existing data
let sample = serde_json::from_value(json!({"data": "sample-data.json"}))?;
let schema = infer_data_schema(&sample)?;
// Validate new data against the schema
let resource = serde_json::from_value(json!({"data": "new-data.json", "dataSchema": schema}))?;
let report = validate_data(&resource)?;
assert!(report.valid, "{:?}", report.errors);Schema-Driven Development
use fairspec::fairspec_metadata::{DataSchema, validate_data_schema};
use fairspec::validate_data;
use serde_json::json;
let api_schema: DataSchema = serde_json::from_value(json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"username": {"type": "string", "minLength": 3},
"email": {"type": "string", "format": "email"},
},
"required": ["id", "username", "email"],
},
},
},
}))?;
// Validate the schema itself first
let schema_result = validate_data_schema(&api_schema, None)?;
assert!(schema_result.valid);
// Then validate an API response against it
let resource =
serde_json::from_value(json!({"data": "response.json", "dataSchema": api_schema}))?;
let report = validate_data(&resource)?;
assert!(report.valid);Automated Testing
use fairspec::fairspec_metadata::load_data_schema;
use fairspec::validate_data;
use serde_json::json;
let schema = load_data_schema("schema.json")?;
let mut failed = false;
for entry in std::fs::read_dir("test-data")? {
let path = entry?.path();
if path.extension().is_none_or(|extension| extension != "json") {
continue;
}
let resource = serde_json::from_value(json!({
"data": path.to_string_lossy(),
"dataSchema": schema,
}))?;
if validate_data(&resource)?.valid {
println!("OK {}", path.display());
} else {
println!("FAIL {}", path.display());
failed = true;
}
}
std::process::exit(if failed { 1 } else { 0 });Examples
API Response Validation
use fairspec::{infer_data_schema, load_data, validate_data};
use serde_json::json;
// Fetch an API response and infer its schema
let remote = serde_json::from_value(json!({"data": "https://api.example.com/users"}))?;
let sample = load_data(&remote)?;
let resource = serde_json::from_value(json!({"data": sample}))?;
let schema = infer_data_schema(&resource)?;
// Validate future responses against it
let new_data = load_data(&remote)?;
let resource = serde_json::from_value(json!({"data": new_data, "dataSchema": schema}))?;
let report = validate_data(&resource)?;
assert!(report.valid, "{:?}", report.errors);Configuration File Validation
use fairspec::validate_data;
use serde_json::json;
let resource = serde_json::from_value(json!({
"data": "config.json",
"dataSchema": {
"type": "object",
"properties": {
"host": {"type": "string"},
"port": {"type": "integer", "minimum": 1, "maximum": 65535},
"ssl": {"type": "boolean"},
},
"required": ["host", "port"],
},
}))?;
let report = validate_data(&resource)?;
assert!(report.valid);Schema Evolution
use fairspec::fairspec_metadata::validate_data_schema;
use fairspec::{infer_data_schema, validate_data};
use serde_json::json;
// Start with the inferred schema from v1 data
let v1 = serde_json::from_value(json!({"data": "data-v1.json"}))?;
let mut schema = infer_data_schema(&v1)?.ok_or("no schema inferred")?;
// Manually evolve to v2 (add an optional property)
if let Some(properties) = schema.get_mut("properties").and_then(|value| value.as_object_mut()) {
properties.insert("new_optional_field".into(), json!({"type": "string"}));
}
// Confirm v2 schema is still well-formed
assert!(validate_data_schema(&schema, None)?.valid);
// Confirm v1 data is still compatible with v2 schema
let resource = serde_json::from_value(json!({"data": "data-v1.json", "dataSchema": schema}))?;
assert!(validate_data(&resource)?.valid);Created with โค and Livemark