0x192/universal-android-debloater · error
Unable to parse
Error message
Unable to parse
What it means
The downloaded uad_lists.json text is parsed with serde_json::from_str(&text).expect("Unable to parse"). The panic means the remote file was fetched but is not valid JSON or no longer matches the expected list schema (HashMap<String, Package>).
Source
Thrown at src/core/uad_lists.rs:190
}
}
type PackageHashMap = HashMap<String, Package>;
pub fn load_debloat_lists(remote: bool) -> (Result<PackageHashMap, PackageHashMap>, bool) {
let cached_uad_lists: PathBuf = CACHE_DIR.join("uad_lists.json");
let mut error = false;
let list: Vec<Package> = if remote {
retry(Fixed::from_millis(1000).take(60), || {
match ureq::get(
"https://raw.githubusercontent.com/0x192/universal-android-debloater/\
main/resources/assets/uad_lists.json",
)
.call()
{
Ok(data) => {
let text = data.into_string().expect("response should be Ok type");
fs::write(cached_uad_lists.clone(), &text).expect("Unable to write file");
let list = serde_json::from_str(&text).expect("Unable to parse");
OperationResult::Ok(list)
}
Err(e) => {
warn!("Could not load remote debloat list: {}", e);
error = true;
OperationResult::Retry(Vec::<Package>::new())
}
}
})
.map_or_else(|_| get_local_lists(), |list| list)
} else {
warn!("Could not load remote debloat list");
get_local_lists()
};
// TODO: Do it without intermediary Vec?
let mut package_lists = HashMap::new();
for p in list {View on GitHub (pinned to 11f27c671c)
Solutions
- Retry the download or verify network path returns the real raw.githubusercontent.com JSON
- Update the app so the Package struct matches the current list schema
- Match on from_str and fall back to get_local_lists() (bundled JSON) on parse failure
- Inspect the downloaded file's first bytes to confirm it is JSON, not an HTML error page
Example fix
// before
let list = serde_json::from_str(&text).expect("Unable to parse");
// after
let list: Vec<Package> = match serde_json::from_str(&text) {
Ok(l) => l,
Err(e) => { warn!("Unable to parse remote list: {}", e); return get_local_lists_retry(); }
}; Defensive patterns
Strategy: fallback
Validate before calling
let text = std::fs::read_to_string("uad_lists.json")?;
if !text.trim_start().starts_with('{') && !text.trim_start().starts_with('[') {
return Err("downloaded list is not JSON (proxy/portal page?)".into());
} Type guard
fn looks_like_json(text: &str) -> bool {
let t = text.trim_start();
t.starts_with('{') || t.starts_with('[')
} Try / catch
let result = std::panic::catch_unwind(|| load_debloat_lists());
if result.is_err() {
eprintln!("Unable to parse remote list — falling back to bundled list");
} Prevention
- Update the app when the upstream list schema changes
- Detect captive-portal/HTML responses before parsing
- Pin or verify the download URL returns the real raw JSON
- Retain the bundled list as an always-available fallback
When it happens
Trigger: A proxy/captive portal returns HTML instead of the JSON (which may still parse-fail after into_string succeeds), the upstream JSON is malformed, or the app's Package struct is out of sync with the repository's list format.
Common situations: Captive portal/ISP injection pages; GitHub serving an error page; app version older than a breaking change in the debloat list format; truncated download.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unable to parse backup file
- response should be Ok type
- Could not write config file to disk!
- Unable to write file
- Can't create cache directory
AI-assisted analysis of 0x192/universal-android-debloater@11f27c671c (2026-09-02).
Data as JSON: /api/errors/96faeecaa749751f.
Report an issue: GitHub.