a-b-street/abstreet · error

read_json needs to end with .json or .geojson

Error message

read_json needs {} to end with .json or .geojson

What it means

abstio::maybe_read_json deserializes JSON files and enforces that the path ends with .json or .geojson before reading. This guards against accidentally passing binary (bincode) files or paths without an extension; otherwise it bails with "read_json needs ... to end with .json or .geojson".

Solutions

  1. Fix the path so it ends with .json or .geojson.
  2. If the file is actually bincode, use abstio::maybe_read_binary instead.
  3. If the path points to a directory, point at the concrete .json file inside it.

Example fix

// before
abstio::maybe_read_json("../data/system/cities.bin", timer)
// after
abstio::maybe_read_binary::<MyType>("../data/system/cities.bin", timer)
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_json_path(path: &str) -> Result<(), String> {
    if path.ends_with(".json") || path.ends_with(".geojson") { Ok(()) } else { Err(format!("{} is not a .json/.geojson path", path)) }
}

Try / catch

if let Err(e) = ensure_json_path(&path) { bail!("{}", e); }
let value: T = abstio::maybe_read_json(path, timer)?;

Prevention

When it happens

Trigger: Calling abstio::read_json / read_object with a path ending in .bin, .txt, no extension, or a different extension; passing a path to a bincode-serialized system file.

Common situations: Confusing binary data files (produced by abstio::write_binary) with JSON ones; path constructed by string concatenation that dropped the extension; passing a directory path instead of a file.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at abstio/src/io.rs:12

use std::collections::BTreeMap;

use anyhow::Result;
use serde::de::DeserializeOwned;

use abstutil::{basename, parent_path, Timer};

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 {

View on GitHub (pinned to 0964f29315)