jaml 0.2.0

A Rust library for parsing and formatting JAML (Just Another Markup Language)
Documentation

JAML - Just Another Markup Language

A Rust library for parsing and formatting JAML, a YAML-inspired data serialization format with explicit integer and binary types.

JAML shares the same data model as JASN but uses indentation-based syntax for a cleaner, more readable appearance. Think "YAML done right" - no implicit type conversions, no ambiguous syntax, explicit types.

Why JAML?

  • Clean YAML-like syntax without the gotchas (no "Norway Problem")
  • Explicit types: 42 is an integer, 42.0 is a float
  • Quoted strings: No implicit type conversion, all string values must be quoted
  • Native binary data: b64"..." and hex"..." literals
  • Timestamps: First-class ts"..." support
  • Flexible indentation: First indent defines base unit (2 spaces, 4 spaces, tabs, etc.)
  • Full serde support: Serialize/deserialize any Rust type

Quick Start

Parsing JAML

use jaml::parse;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let value = parse(r#"
name: "Alice"
age: 30
balance: 1234.56
data: b64"SGVsbG8="
tags:
  - "rust"
  - "yaml"
  - "parser"
    "#)?;
    
    println!("{:#?}", value);
    Ok(())
}

Serde Integration

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Config {
    name: String,
    version: u32,
    enabled: bool,
    timeout_ms: Option<u64>,
    tags: Vec<String>,
}

let config = Config {
    name: "My App".to_string(),
    version: 1,
    enabled: true,
    timeout_ms: Some(5000),
    tags: vec!["rust".to_string(), "jaml".to_string()],
};

// Serialize to JAML
let jaml = jaml::to_string(&config)?;

// Deserialize from JAML
let parsed: Config = jaml::from_str(&jaml)?;

Example

A comprehensive example showing all supported types:

# Null
nothing: null

# Booleans
active: true
disabled: false

# Integers (distinct from floats)
count: 42
negative: -123
hex_value: 0xFF
binary_value: 0b1010
octal_value: 0o755
with_underscores: 1_000_000

# Floats
pi: 3.14159
scientific: 1.5e10
infinity: inf
not_a_number: nan

# Strings (must be quoted)
name: "Alice"
message: "Hello, World!"
unicode: "Hello \u4E16\u754C"

# Binary data
data_b64: b64"SGVsbG8gV29ybGQh"
data_hex: hex"48656c6c6f"

# Timestamps
created: ts"2024-01-15T12:30:45Z"
updated: ts"2024-01-15T12:30:45.123Z"

# Lists (block syntax)
numbers:
  - 1
  - 2
  - 3

# Nested lists
matrix:
  -
    - 1
    - 2
  -
    - 3
    - 4

# Maps (nested structures)
person:
  name: "Bob"
  age: 25
  address:
    street: "123 Main St"
    city: "Portland"
    zip: "97201"

# Inline lists and maps (compact single-line syntax)
inline_list: [1, 2, 3]
inline_map: {x: 10, y: 20}

# Lists of maps
users:
  - name: "Alice"
    role: "admin"
  - name: "Bob"
    role: "user"

Features

Data Types

  • Null: null
  • Boolean: true, false
  • Integer (i64): 42, -123, 0xFF, 0b1010, 0o755
  • Float (f64): 3.14, 1e10, .5, 5., inf, nan
  • String: "quoted" or 'quoted' (always quoted)
  • Binary: b64"base64..." or hex"hexdigits..."
  • Timestamp: ts"2024-01-15T12:30:45Z"
  • List: Block syntax or inline [...]
  • Map: Block syntax or inline {...}

Syntax Features

  • Indentation-based: Clean structure without braces
  • Flexible indentation: First indent (2 spaces, 4 spaces, tabs) defines base unit
  • Line comments: # starts a comment
  • Unquoted keys: Map keys can be identifiers (name: "value")
  • Quoted keys: For special characters ("key-with-dash": "value")
  • Inline syntax: Compact [...] and {...} for single-line structures
  • Trailing commas: Allowed in inline lists/maps

No YAML Gotchas

JAML avoids common YAML pitfalls:

# YAML 1.1 (problematic)
NO: Norway    # NO becomes false!
yes: Yemen    # yes becomes true!
# JAML (explicit)
NO: "Norway"    # NO is an identifier key, "Norway" is a quoted string
yes: "Yemen"    # yes is an identifier key, "Yemen" is a quoted string

All string values must be quoted - no implicit type conversion!

Installation

[dependencies]
jaml = "0.2"

For serde support (enabled by default):

[dependencies]
jaml = { version = "0.2", features = ["serde"] }

API Overview

Parsing

use jaml::{parse, Value};

// Parse to AST
let value: Value = jaml::parse(text)?;

// With serde
let config: MyStruct = jaml::from_str(text)?;

Formatting

use jaml::{format, formatter::Options};

// Format a value
let text = jaml::format(&value);

// With custom options
let opts = Options::new()
    .with_sort_keys(true)
    .with_quote_style(formatter::QuoteStyle::PreferDouble);
let text = jaml::format_with_opts(&value, &opts);

// With serde
let text = jaml::to_string(&my_struct)?;

Documentation

Differences from YAML

  1. Explicit string values: All string values must be quoted
  2. Integer/Float distinction: 42 and 42.0 are different types
  3. Binary type: Native b64"..." and hex"..." literals
  4. Timestamp type: Native ts"..." literals
  5. No implicit conversions: No yes/no/on/off boolean conversion
  6. Flexible indentation: First indent defines base unit
  7. Single document: No multi-document support
  8. No anchors/aliases: No &anchor or *alias
  9. No tags: No !!type support
  10. Simpler, explicit syntax: Focused subset with clear rules

Differences from JASN

Important: JAML is not a superset of JASN. Unlike YAML/JSON (where JASN is a superset of JSON), JAML and JASN use intentionally disjoint syntax styles.

Both share the same data model (jasn-core::Value) and can represent identical data, but use different syntax:

Feature JASN JAML
Structure Braces {} and brackets [] Indentation-based
Multi-line structures Allowed in {} and [] Only via indentation (block syntax)
Inline structures [...] and {...} with newlines allowed [...] and {...} must be single-line
Comments Block /* */ Line # only
String values Optional quotes Must be quoted
Trailing commas Everywhere Only in inline [] and {}

Design Philosophy: The syntax incompatibility is intentional. Each format has its own consistent style:

  • JASN: Braces/brackets everywhere, block comments, flexible quoting
  • JAML: Indentation for structure, line comments, explicit quoting
  • Compatibility: Via the shared Value type - convert between formats through the AST
// JASN text -> Value -> JAML text
let jasn = r#"{ name: "Alice", age: 30 }"#;
let value = jasn::parse(jasn)?;
let jaml = jaml::format(&value);

// JAML text -> Value -> JASN text
let jaml = "name: \"Alice\"\nage: 30";
let value = jaml::parse(jaml)?;
let jasn = jasn::format(&value);

License

MIT License - see LICENSE file for details.