a-b-street/abstreet · error

Couldn't read_json( )

Error message

Couldn't read_json({}): {}

What it means

abstio::read_json is an infallible wrapper around maybe_read_json: it deserializes a JSON file at the given path, and if the read or parse fails (missing file, bad permissions, malformed JSON, schema mismatch) it panics with the path and underlying error. It exists for cases where the caller considers the file mandatory.

Solutions

  1. Verify the file exists at the exact resolved path (print the path; check with std::path::Path::exists or abstio::file_exists)
  2. Switch to abstio::maybe_read_json and handle the Err gracefully instead of panicking when the file may legitimately be absent
  3. Regenerate or re-download the data (run the importer / data sync) if the file is missing because data wasn't built
  4. Validate the JSON against the expected schema / check serde version compatibility if the error is a parse or deserialization failure

Example fix

// before
let edits: EditMap = abstio::read_json(path, timer); // panics if missing
// after
let edits: EditMap = match abstio::maybe_read_json(path.clone(), timer) {
    Ok(obj) => obj,
    Err(err) => {
        eprintln!("optional file {} unavailable: {}", path, err);
        EditMap::default()
    }
};
Defensive patterns

Strategy: try-catch

Validate before calling

if !abstio::file_exists(path.clone()) {
    eprintln!("required file missing: {}", path);
    return;
}
let data: T = abstio::read_json(path, timer);

Try / catch

// Prefer the fallible API instead of catching the panic:
match abstio::maybe_read_json::<T>(path.clone(), timer) {
    Ok(obj) => obj,
    Err(err) => {
        eprintln!("Couldn't read {}: {}", path, err);
        Default::default() // or propagate the error
    }
}

Prevention

When it happens

Trigger: Any call to abstio::read_json::<T>(path, timer) where maybe_read_json fails: the file does not exist at the resolved path, the JSON is syntactically invalid, or the JSON does not match T's serde expectations. Commonly via wrappers like CityName::input_path-based loads of input/system files.

Common situations: Typo in the path or scenario/map name, calling read_json on an optional file that hasn't been generated yet, running before data has been imported/downloaded, hand-edited JSON with a syntax error, or deserializing an older/newer file version into a changed struct.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/f37f7c30a84ec19e. Report an issue: GitHub.

Appendix: source

Thrown at abstio/src/io.rs:26

use crate::{list_dir, maybe_read_binary, slurp_file};

pub fn maybe_read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T> {
    if !path.ends_with(".json") && !path.ends_with(".geojson") {
        bail!("read_json needs {} to end with .json or .geojson", path);
    }

    timer.start(format!("parse {}", path));
    // TODO timer.read_file isn't working here. And we need to call stop() if there's no file.
    let result: Result<T> =
        slurp_file(&path).and_then(|raw| serde_json::from_slice(&raw).map_err(|err| err.into()));
    timer.stop(format!("parse {}", path));
    result
}

pub fn read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
    match maybe_read_json(path.clone(), timer) {
        Ok(obj) => obj,
        Err(err) => panic!("Couldn't read_json({}): {}", path, err),
    }
}

pub fn read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
    match maybe_read_binary(path.clone(), timer) {
        Ok(obj) => obj,
        Err(err) => panic!("Couldn't read_binary({}): {}", path, err),
    }
}

/// May be a JSON or binary file
pub fn read_object<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T> {
    if path.ends_with(".bin") {
        maybe_read_binary(path, timer)
    } else {
        maybe_read_json(path, timer)
    }
}

View on GitHub (pinned to 0964f29315)