Skip to content
sformisanoPublic

About

A Rust library that validates JSON with JSON Schema and renders it as Markdown via Jinja templates.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

json2md

json2md is a Rust library that validates JSON and renders it as Markdown. Keep a document's content separate from its layout, and check its fields and values before producing the document.

From JSON to Markdown

This example creates a release note with a title, summary, and list of changes.

1. Start with the data

The JSON input holds the note's content:

{
  "title": "Release 0.1",
  "summary": "JSON data becomes a checked Markdown document.",
  "changes": [
    "Validate input before rendering.",
    "Reuse compiled schemas and templates."
  ]
}

These values describe the content. The headings and list formatting belong in the template introduced below.

2. Define the accepted data

JSON Schema describes which fields and values are accepted. The note's schema requires a title and summary as strings, and changes as an array of strings:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "title": { "type": "string" },
    "summary": { "type": "string" },
    "changes": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["title", "summary", "changes"],
  "additionalProperties": false
}

Declaring a property in properties describes its value. Listing it in required makes its presence mandatory. additionalProperties: false rejects fields that the schema does not declare.

3. Describe the presentation

A template combines fixed Markdown with values from the JSON input. Templates use MiniJinja syntax: {{ ... }} inserts a value, and {% ... %} controls a loop or condition. JSON object properties become template variables.

# {{ title }}

{{ summary }}

## Changes
{% for change in changes %}
- {{ change }}{% endfor %}

Use the Rust library

The library requires Rust 1.98 or later. Add these dependencies to your Cargo.toml:

[dependencies]
json2md = "0.1"
serde_json = "1.0"

See the API reference for public types and methods.

Compile the view and render

A View compiles one schema and one template. Its render method validates the input before executing the template, then returns the complete Markdown string.

The complete runnable example embeds the release-note files:

use json2md::View;
use serde_json::Value;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let schema: Value = serde_json::from_str(include_str!("assets/note.schema.json"))?;
    let input: Value = serde_json::from_str(include_str!("assets/note.json"))?;
    let view = View::new(&schema, include_str!("assets/note.j2"))?;

    print!("{}", view.render(&input)?);
    Ok(())
}

Run it from the repository root:

cargo run --locked -p json2md --example render

Standard output matches the expected Markdown, including its final newline:

# Release 0.1

JSON data becomes a checked Markdown document.

## Changes

- Validate input before rendering.
- Reuse compiled schemas and templates.

Reuse the view

Keep the View to render more notes without compiling the schema and template again. Each input is still validated. The view owns its compiled assets, so the original schema and template can be dropped.

Handle invalid input

Input that fails the schema returns Error::Validation with field paths and diagnostics. Using the same view, replace the title with a number:

use json2md::Error;
use serde_json::json;

let invalid = json!({
    "title": 42,
    "summary": "JSON data becomes a checked Markdown document.",
    "changes": []
});

match view.render(&invalid) {
    Ok(markdown) => println!("{markdown}"),
    Err(Error::Validation(errors)) => {
        for issue in errors.issues() {
            eprintln!("{}: {}", issue.instance_path(), issue);
        }
    }
    Err(error) => eprintln!("{error}"),
}

The diagnostic for this input is:

/title: 42 is not of type "string"

The /title path identifies the rejected property. No template execution occurs for this input. Match typed error variants to handle failures; diagnostic wording and issue ordering can change with dependencies.

Use view.validate(&input) when you only need validation. Rendering always validates; there is no unchecked rendering method.

Handle optional fields

The note currently requires summary. Remove it from the schema's required array to make it optional. The original template's {{ summary }} then fails with Error::Render when that property is absent. Guard the optional section explicitly:

{% if summary is defined %}
{{ summary }}
{% endif %}

Some MiniJinja filters and tests handle missing values without an error. Keep a property required in the schema when every document must supply it. A present JSON null differs from an absent property; this note's string schema rejects null for summary.

View::new checks the schema and template separately. It does not prove that the template can render every input accepted by the schema.

Rendering behavior

The renderer preserves Markdown and HTML characters without automatic escaping. For example, a JSON string containing **bold** produces **bold** in the output. Choose how to escape or sanitize values for your Markdown renderer. The final template newline is preserved.

Library callers load files and write output. Schema references within the supplied document are supported; external schema retrieval and template file loading are disabled. Known schema formats are validated. Compile-time schema-template compatibility checking is outside v0.1. See the architecture contract for schema drafts, numeric limits, errors, and trust boundaries.

Render files with the CLI

The command-line tool uses the same library to render files. Download a prebuilt binary from GitHub Releases. See binary installation for platforms and checksum verification.

Or install the json2md-cli package from crates.io with Rust 1.98 or later. Its binary is named json2md:

cargo install --locked json2md-cli

Supply your JSON, schema, and template files. The schema and template are required:

json2md render input.json --schema schema.json --template view.j2

Markdown goes to standard output. Use --output PATH to write a file instead:

json2md render input.json --schema schema.json --template view.j2 --output document.md

Run the same release-note example from the repository root without installing:

cargo run --locked -p json2md-cli -- render examples/assets/note.json \
  --schema examples/assets/note.schema.json --template examples/assets/note.j2

Every render validates its input. Failures produce diagnostics on standard error and a nonzero exit status. Validation or rendering failures produce no Markdown. An unsuccessful operation preserves an existing output file. Output cannot replace an input, schema, or template file. See the CLI contract for output and diagnostic details.

Development

Repository commands run from the workspace root. To install the CLI from source, use cargo install --locked --path crates/json2md-cli.

The library integration tests compare real template files against independently written Markdown files. They also exercise invalid schemas, invalid data, strict missing values, schema references, formats, and view reuse. The CLI integration tests invoke the binary with real files. They check exact output, diagnostics, failure status, and preservation of inputs and existing output.

cargo fmt --all -- --check
cargo test --locked --workspace --all-targets
cargo test --locked --workspace --doc
cargo clippy --locked --workspace --all-targets -- -D warnings

Generate API documentation with cargo doc --locked --workspace --no-deps. The CI workflow also compares library examples and CLI output with committed Markdown files. The GitLab fixtures also exercise finding and summary bodies as offline regression tests. They are excluded from release packages. See agent skills for repository authoring guidance and README guidance for documentation examples. See publishing for package verification and release commands.

License

Licensed under MIT OR Apache-2.0, at your choice. See LICENSE for both texts.

About

A Rust library that validates JSON with JSON Schema and renders it as Markdown via Jinja templates.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages