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

  1. Retry the download or verify network path returns the real raw.githubusercontent.com JSON
  2. Update the app so the Package struct matches the current list schema
  3. Match on from_str and fall back to get_local_lists() (bundled JSON) on parse failure
  4. 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

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

Related errors


AI-assisted analysis of 0x192/universal-android-debloater@11f27c671c (2026-09-02). Data as JSON: /api/errors/96faeecaa749751f. Report an issue: GitHub.