JSON Formatter — Rust source
Beautify, minify and validate JSON instantly. Paste, format, copy - all in your browser. Share a link to your exact input.
This is the Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON formatter — parse, pretty-print (2-space indent), and minify.
//
// Language: Rust
// CosmoDev polyglot showcase port of the `json-formatter` tool.
// Ported from src/tools/JsonFormatter.tsx — display source, part of CosmoDev's
// polyglot tool pages.
//
// The Rust standard library ships no JSON engine, so — unlike the
// TypeScript/JavaScript/Go/PHP/Python ports, which lean on a stdlib JSON
// implementation — this file includes a small hand-written recursive-descent
// parser and serializer (std only, no serde and no external crates). Object
// members are stored in a Vec<(String, Json)>, which preserves source
// insertion order just like a JavaScript engine.
//
// Parity caveats: numbers are held as f64 (matching JS Number), so integers
// beyond 2^53 lose precision and Rust's f64 Display diverges from ECMAScript's
// Number::toString near the exponential-notation thresholds (e.g. 1e21 prints
// in full rather than as "1e+21"). For ordinary integers and decimals the
// output is byte-identical to JSON.stringify.
#![forbid(unsafe_code)]
use std::fmt::Write as _;
/// A parsed JSON value. Object members live in a Vec to preserve source order.
#[derive(Debug, Clone, PartialEq)]
pub enum Json {
Null,
Bool(bool),
Number(f64),
Str(String),
Array(Vec<Json>),
Object(Vec<(String, Json)>),
}
/// The format result. `error == None` means success or empty input.
#[derive(Debug, Clone, PartialEq)]
pub struct FormatResult {
pub output: String,
pub error: Option<String>,
}
/// Selects the output shape.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Beautify,
Minify,
}
/// A parse failure: byte offset + message.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub offset: usize,
pub message: &'static str,
}
/// Parse and re-emit a JSON document the way the live island does. `indent` is
/// the number of spaces per nesting level for Mode::Beautify (pass 2 to match
/// the UI). Empty/whitespace-only input yields empty output, no error.
pub fn format_json(input: &str, mode: Mode, indent: usize) -> FormatResult {
let trimmed = input.trim();
if trimmed.is_empty() {
return FormatResult { output: String::new(), error: None };
}
match Parser::new(trimmed).parse_document() {
Ok(value) => {
let pad = " ".repeat(indent);
let mut out = String::new();
match mode {
Mode::Minify => value.write_compact(&mut out),
Mode::Beautify => value.write_pretty(&mut out, 0, &pad),
}
FormatResult { output: out, error: None }
}
Err(e) => FormatResult {
output: String::new(),
error: Some(e.message.to_string()),
},
}
}
impl Json {
fn write_compact(&self, out: &mut String) {
match self {
Json::Null => out.push_str("null"),
Json::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Json::Number(n) => {
let _ = write!(out, "{n}");
}
Json::Str(s) => write_json_string(out, s),
Json::Array(items) => {
out.push('[');
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
v.write_compact(out);
}
out.push(']');
}
Json::Object(members) => {
out.push('{');
for (i, (k, v)) in members.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_json_string(out, k);
out.push(':');
v.write_compact(out);
}
out.push('}');
}
}
}
fn write_pretty(&self, out: &mut String, depth: usize, pad: &str) {
match self {
Json::Array(items) if !items.is_empty() => {
out.push('[');
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('\n');
push_indent(out, depth + 1, pad);
v.write_pretty(out, depth + 1, pad);
}
out.push('\n');
push_indent(out, depth, pad);
out.push(']');
}
Json::Object(members) if !members.is_empty() => {
out.push('{');
for (i, (k, v)) in members.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('\n');
push_indent(out, depth + 1, pad);
write_json_string(out, k);
out.push_str(": ");
v.write_pretty(out, depth + 1, pad);
}
out.push('\n');
push_indent(out, depth, pad);
out.push('}');
}
// Scalars and empty containers render identically in both modes.
other => other.write_compact(out),
}
}
}
fn push_indent(out: &mut String, depth: usize, pad: &str) {
for _ in 0..depth {
out.push_str(pad);
}
}
/// Emit a JSON string literal, escaping per RFC 8259. Like JSON.stringify it
/// leaves '/' unescaped and emits control characters below U+0020 as \uXXXX.
fn write_json_string(out: &mut String, s: &str) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
}
/// A minimal recursive-descent JSON parser that builds a Json value tree.
struct Parser<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Parser<'a> {
fn new(text: &'a str) -> Self {
Self { bytes: text.as_bytes(), pos: 0 }
}
fn err(&self, message: &'static str) -> ParseError {
ParseError { offset: self.pos, message }
}
fn peek(&self) -> Option<u8> {
self.bytes.get(self.pos).copied()
}
fn bump(&mut self) {
self.pos += 1;
}
/// Advance one UTF-8 codepoint (leading byte + continuation bytes). Only
/// called on a valid &str, so this never splits a codepoint.
fn bump_rune(&mut self) {
self.pos += 1;
while matches!(self.peek(), Some(b) if (b & 0xC0) == 0x80) {
self.pos += 1;
}
}
fn skip_ws(&mut self) {
while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
self.pos += 1;
}
}
fn parse_document(&mut self) -> Result<Json, ParseError> {
self.skip_ws();
let v = self.parse_value()?;
self.skip_ws();
if self.peek().is_some() {
return Err(self.err("unexpected trailing characters"));
}
Ok(v)
}
fn parse_value(&mut self) -> Result<Json, ParseError> {
match self.peek() {
Some(b'{') => self.parse_object(),
Some(b'[') => self.parse_array(),
Some(b'"') => Ok(Json::Str(self.parse_string()?)),
Some(b't') | Some(b'f') => self.parse_bool(),
Some(b'n') => self.parse_null(),
Some(b'-') | Some(b'0'..=b'9') => self.parse_number(),
_ => Err(self.err("unexpected token")),
}
}
fn parse_object(&mut self) -> Result<Json, ParseError> {
self.bump(); // consume '{'
self.skip_ws();
let mut members = Vec::new();
if self.peek() == Some(b'}') {
self.bump();
return Ok(Json::Object(members));
}
loop {
self.skip_ws();
if self.peek() != Some(b'"') {
return Err(self.err("expected string key"));
}
let key = self.parse_string()?;
self.skip_ws();
if self.peek() != Some(b':') {
return Err(self.err("expected ':' after key"));
}
self.bump();
self.skip_ws();
let value = self.parse_value()?;
members.push((key, value));
self.skip_ws();
match self.peek() {
Some(b',') => self.bump(),
Some(b'}') => {
self.bump();
return Ok(Json::Object(members));
}
_ => return Err(self.err("expected ',' or '}'")),
}
}
}
fn parse_array(&mut self) -> Result<Json, ParseError> {
self.bump(); // consume '['
self.skip_ws();
let mut items = Vec::new();
if self.peek() == Some(b']') {
self.bump();
return Ok(Json::Array(items));
}
loop {
self.skip_ws();
items.push(self.parse_value()?);
self.skip_ws();
match self.peek() {
Some(b',') => self.bump(),
Some(b']') => {
self.bump();
return Ok(Json::Array(items));
}
_ => return Err(self.err("expected ',' or ']'")),
}
}
}
fn parse_string(&mut self) -> Result<String, ParseError> {
self.bump(); // opening '"'
let mut s = String::new();
loop {
match self.peek() {
None => return Err(self.err("unterminated string")),
Some(b'"') => {
self.bump();
return Ok(s);
}
Some(b'\\') => {
self.bump();
self.parse_escape(&mut s)?;
}
Some(c) if c <= 0x1F => {
return Err(self.err("unescaped control character in string"));
}
Some(_) => {
let start = self.pos;
self.bump_rune();
// Input is a valid &str, so the consumed slice is UTF-8.
s.push_str(std::str::from_utf8(&self.bytes[start..self.pos]).unwrap());
}
}
}
}
fn parse_escape(&mut self, s: &mut String) -> Result<(), ParseError> {
match self.peek() {
Some(b'"') => { self.bump(); s.push('"'); Ok(()) }
Some(b'\\') => { self.bump(); s.push('\\'); Ok(()) }
Some(b'/') => { self.bump(); s.push('/'); Ok(()) }
Some(b'b') => { self.bump(); s.push('\u{08}'); Ok(()) }
Some(b'f') => { self.bump(); s.push('\u{0c}'); Ok(()) }
Some(b'n') => { self.bump(); s.push('\n'); Ok(()) }
Some(b'r') => { self.bump(); s.push('\r'); Ok(()) }
Some(b't') => { self.bump(); s.push('\t'); Ok(()) }
Some(b'u') => { self.bump(); let cp = self.parse_hex4()?; self.decode_surrogate(s, cp) }
_ => Err(self.err("invalid escape sequence")),
}
}
/// Enforce correct UTF-16 surrogate pairing for '\u' escapes.
fn decode_surrogate(&mut self, s: &mut String, cp: u16) -> Result<(), ParseError> {
match cp {
0xD800..=0xDBFF => {
if self.peek() != Some(b'\\') {
return Err(self.err("dangling high surrogate"));
}
self.bump();
if self.peek() != Some(b'u') {
return Err(self.err("expected '\\u' for surrogate pair"));
}
self.bump();
let lo = self.parse_hex4()?;
match lo {
0xDC00..=0xDFFF => {
let scalar = 0x10000
+ (((cp as u32 - 0xD800) << 10) | (lo as u32 - 0xDC00));
if let Some(ch) = char::from_u32(scalar) {
s.push(ch);
}
Ok(())
}
_ => Err(self.err("invalid low surrogate after high surrogate")),
}
}
0xDC00..=0xDFFF => Err(self.err("unexpected low surrogate")),
_ => {
if let Some(ch) = char::from_u32(cp as u32) {
s.push(ch);
}
Ok(())
}
}
}
/// Read exactly four hexadecimal digits following a '\u'.
fn parse_hex4(&mut self) -> Result<u16, ParseError> {
let mut value: u16 = 0;
for _ in 0..4 {
let b = match self.peek() {
Some(b) => b,
None => return Err(self.err("incomplete '\\u' escape")),
};
let d = match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => return Err(self.err("invalid hex digit in '\\u' escape")),
};
value = value * 16 + d as u16;
self.bump();
}
Ok(value)
}
fn parse_number(&mut self) -> Result<Json, ParseError> {
let start = self.pos;
if self.peek() == Some(b'-') {
self.bump();
}
match self.peek() {
Some(b'0') => self.bump(),
Some(b'1'..=b'9') => {
self.bump();
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
}
_ => return Err(ParseError { offset: start, message: "invalid number" }),
}
if self.peek() == Some(b'.') {
self.bump();
if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(self.err("expected digit after decimal point"));
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
}
if matches!(self.peek(), Some(b'e') | Some(b'E')) {
self.bump();
if matches!(self.peek(), Some(b'+') | Some(b'-')) {
self.bump();
}
if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(self.err("expected digit in exponent"));
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.bump();
}
}
let text = std::str::from_utf8(&self.bytes[start..self.pos]).unwrap();
match text.parse::<f64>() {
Ok(n) => Ok(Json::Number(n)),
Err(_) => Err(self.err("invalid number")),
}
}
fn parse_bool(&mut self) -> Result<Json, ParseError> {
if self.consume(b"true") {
Ok(Json::Bool(true))
} else if self.consume(b"false") {
Ok(Json::Bool(false))
} else {
Err(self.err("invalid literal"))
}
}
fn parse_null(&mut self) -> Result<Json, ParseError> {
if self.consume(b"null") {
Ok(Json::Null)
} else {
Err(self.err("invalid literal"))
}
}
/// Match a literal keyword at the current position; on success advance.
fn consume(&mut self, kw: &[u8]) -> bool {
if self.bytes.get(self.pos..self.pos + kw.len()) == Some(kw) {
self.pos += kw.len();
true
} else {
false
}
}
}
fn main() {
let sample = r#"{"name":"CosmoDev","tools":["json","base64"],"free":true,"nested":{"ok":1}}"#;
match format_json(sample, Mode::Beautify, 2) {
FormatResult { error: Some(e), .. } => println!("error: {e}"),
FormatResult { output, .. } => println!("{output}"),
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →