Data formats

JSON5

JSON5 and JSON with comments read as values: tsconfig.json and editor settings, with comments, trailing commas and unquoted keys.

Version 1.0.0 · 3 functions · 1 crate

#json#config#settings#import

Functions

What use json5.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use json5_parse, json5_read_file from json5 (Quillo 0.6).

json5_parse(text: str) → anyor an error

JSON5 text as a value: // and /* */ comments, trailing commas, 'single' quotes, unquoted keys, hex numbers, +Infinity and NaN (as null), and plain JSON too. The error says the line and column.

json5_read_file(path: str) → anyor an error

A JSON5 or JSONC file (tsconfig.json, .vscode/settings.json) read as json5_parse reads text.

jsonc_strip(text: str) → stror an error

text with its comments and trailing commas taken out, as plain JSON text that any JSON reader takes; strings are kept as they are, comments inside them too. For JSONC that must go on to a strict parser unchanged otherwise.

Get it

$ quillo install json5               # json5.qwer into this folder$ quillo install json5 --example     # and its example, json5.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/json5.qwer$ curl -O https://quillolang.org/plugins/files/json5.qwe$ curl -O https://quillolang.org/plugins/files/json5_sample.jsonc

Then, in your program, all of it or only what you call:

main.qwe
use json5.qweruse json5_parse, json5_read_file from json5      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
json5 = "0.4"

Example

json5.qwe
// Reading a project's tsconfig.json (JSON with comments) and a JSON5 settings text.////   quillo run json5.qwe      then open http://localhost:8080/tsconfiguse "json5.qwer"route GET /tsconfig {    let ts = json5_read_file("json5_sample.jsonc")?    { target: ts.compilerOptions.target, strict: ts.compilerOptions.strict, include: ts.include, builtin_json_refuses: is_err(json.parse(read_file("json5_sample.jsonc")?)) }}route GET /settings = json5_parse("{ theme: 'dark', fontSize: 14, maxSize: 0x400, plugins: ['git', 'quillo',], /* off for now */ telemetry: false }")?route GET /strip = { json: json.parse(jsonc_strip(read_file("json5_sample.jsonc")?)?)? }
$ quillo run json5.qwe$ curl 'localhost:8080/tsconfig'$ curl 'localhost:8080/settings'$ curl 'localhost:8080/strip'

Source

json5.qwer
//! JSON5 and JSON with comments read as values: tsconfig.json, VS Code settings, devcontainer and Deno files, with comments, trailing commas, single quotes and unquoted keys.//! version: 1.0.0//!//! [dependencies]//! json5 = "0.4"use serde_json::Value;/// Texts up to 10 MB are read.const MAX: usize = 10 * 1024 * 1024;/// The deepest nesting of brackets outside strings: the parser recurses, and text nested thousands deep would overflow/// the server's stack, so it is refused first.fn too_deep(text: &str, max: usize) -> bool {    let (mut depth, mut quote, mut esc) = (0usize, None::<char>, false);    for c in text.chars() {        if let Some(q) = quote {            if esc { esc = false } else if c == '\\' { esc = true } else if c == q { quote = None }            continue;        }        match c {            '"' | '\'' => quote = Some(c),            '[' | '{' | '(' => {                depth += 1;                if depth > max {                    return true;                }            }            ']' | '}' | ')' => depth = depth.saturating_sub(1),            _ => {}        }    }    false}/// JSON5 text as a value: `//` and `/* */` comments, trailing commas, `'single'` quotes, unquoted keys, hex numbers,/// `+Infinity` and `NaN` (as null), and plain JSON too. The error says the line and column.pub fn json5_parse(text: &str) -> Result<Value, String> {    if text.len() > MAX {        return Err("texts up to 10 MB".into());    }    if too_deep(text, 128) {        return Err("nested more than 128 deep".into());    }    let v: Value = json5::from_str(text).map_err(|e| format!("not JSON5: {}", e))?;    Ok(v)}/// A JSON5 or JSONC file (tsconfig.json, .vscode/settings.json) read as json5_parse reads text.pub fn json5_read_file(path: &str) -> Result<Value, String> {    let size = std::fs::metadata(path).map_err(|e| format!("{}: {}", path, e))?.len();    if size as usize > MAX {        return Err(format!("{}: files up to 10 MB", path));    }    json5_parse(&std::fs::read_to_string(path).map_err(|e| format!("{}: {}", path, e))?)}/// `text` with its comments and trailing commas taken out, as plain JSON text that any JSON reader takes; strings are kept as/// they are, comments inside them too. For JSONC that must go on to a strict parser unchanged otherwise.pub fn jsonc_strip(text: &str) -> Result<String, String> {    if text.len() > MAX {        return Err("texts up to 10 MB".into());    }    let b: Vec<char> = text.chars().collect();    let mut out = String::with_capacity(text.len());    let mut i = 0;    let mut in_str: Option<char> = None;    while i < b.len() {        let c = b[i];        if let Some(q) = in_str {            out.push(c);            if c == '\\' && i + 1 < b.len() {                out.push(b[i + 1]);                i += 2;                continue;            }            if c == q {                in_str = None;            }            i += 1;            continue;        }        if c == '"' {            in_str = Some(c);            out.push(c);            i += 1;        } else if c == '/' && b.get(i + 1) == Some(&'/') {            while i < b.len() && b[i] != '\n' {                i += 1;            }        } else if c == '/' && b.get(i + 1) == Some(&'*') {            i += 2;            while i < b.len() && !(b[i] == '*' && b.get(i + 1) == Some(&'/')) {                i += 1;            }            i += 2;        } else if c == ',' {            // A comma followed only by blanks and comments before `}` or `]` goes.            let mut j = i + 1;            loop {                while j < b.len() && b[j].is_whitespace() {                    j += 1;                }                if b.get(j) == Some(&'/') && b.get(j + 1) == Some(&'/') {                    while j < b.len() && b[j] != '\n' { j += 1; }                } else if b.get(j) == Some(&'/') && b.get(j + 1) == Some(&'*') {                    j += 2;                    while j < b.len() && !(b[j] == '*' && b.get(j + 1) == Some(&'/')) { j += 1; }                    j += 2;                } else {                    break;                }            }            if !matches!(b.get(j), Some('}') | Some(']')) {                out.push(c);            }            i += 1;        } else {            out.push(c);            i += 1;        }    }    Ok(out)}
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On JSON5: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.