{"record":{"id":"40995ca760a5f973","repo":"a-b-street/abstreet","slug":"read-json-needs-to-end-with-json-or-geojson","errorCode":null,"errorMessage":"read_json needs {} to end with .json or .geojson","messagePattern":"read_json needs (.+?) to end with \\.json or \\.geojson","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"abstio/src/io.rs","lineNumber":12,"sourceCode":"use std::collections::BTreeMap;\n\nuse anyhow::Result;\nuse serde::de::DeserializeOwned;\n\nuse abstutil::{basename, parent_path, Timer};\n\nuse crate::{list_dir, maybe_read_binary, slurp_file};\n\npub fn maybe_read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T> {\n    if !path.ends_with(\".json\") && !path.ends_with(\".geojson\") {\n        bail!(\"read_json needs {} to end with .json or .geojson\", path);\n    }\n\n    timer.start(format!(\"parse {}\", path));\n    // TODO timer.read_file isn't working here. And we need to call stop() if there's no file.\n    let result: Result<T> =\n        slurp_file(&path).and_then(|raw| serde_json::from_slice(&raw).map_err(|err| err.into()));\n    timer.stop(format!(\"parse {}\", path));\n    result\n}\n\npub fn read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {\n    match maybe_read_json(path.clone(), timer) {\n        Ok(obj) => obj,\n        Err(err) => panic!(\"Couldn't read_json({}): {}\", path, err),\n    }\n}\n\npub fn read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/abstio/src/io.rs#L1-L30","documentation":"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\".","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the path so it ends with .json or .geojson.","If the file is actually bincode, use abstio::maybe_read_binary instead.","If the path points to a directory, point at the concrete .json file inside it."],"exampleFix":"// before\nabstio::maybe_read_json(\"../data/system/cities.bin\", timer)\n// after\nabstio::maybe_read_binary::<MyType>(\"../data/system/cities.bin\", timer)","handlingStrategy":"validation","validationCode":"fn ensure_json_path(path: &str) -> Result<(), String> {\n    if path.ends_with(\".json\") || path.ends_with(\".geojson\") { Ok(()) } else { Err(format!(\"{} is not a .json/.geojson path\", path)) }\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = ensure_json_path(&path) { bail!(\"{}\", e); }\nlet value: T = abstio::maybe_read_json(path, timer)?;","preventionTips":["Use maybe_read_binary for .bin/bincode files, read_json only for .json/.geojson.","Build paths with helpers like abstio::path rather than raw string concatenation.","Keep a single constant for data file extensions."],"tags":["file-io","validation","json"],"backgroundTag":"invalid-argument-format","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}