BigPizzaV3/CodexPlusPlus · error
必须是 JSON 对象
Error message
{field_name} 必须是 JSON 对象 What it means
After parse_model_string_map successfully parses the JSON, it requires the top-level value to be a JSON object (serde_json::Value::as_object). If the field parses to an array, string, number, or null, it throws "{field_name} 必须是 JSON 对象". The map format is mandatory because entries are read as key→string pairs.
Solutions
- Wrap the value in a JSON object: {"<model-slug>": "<value>"} — top level must be `{...}`.
- If you have a list of models, convert each to a key with a value rather than an array.
- Validate with code: parse then check it is an object before assigning to the profile field.
- Clear the field if no mapping is needed (empty string is valid).
Example fix
// before
model_windows = "[\"deepseek-v4-pro\"]"
// after
model_windows = "{\"deepseek-v4-pro\": \"1000000\"}" Defensive patterns
Strategy: type-guard
Validate before calling
fn is_json_object(json: &str) -> bool {
serde_json::from_str::<serde_json::Value>(json)
.map(|v| v.is_object())
.unwrap_or(false)
} Type guard
fn is_object(v: &serde_json::Value) -> bool { v.is_object() } Try / catch
match normalize_relay_profile_for_storage(profile) {
Err(e) if e.to_string().contains("必须是 JSON 对象") => {
// coerce/wrap the value into {"key": value} or reject in the UI
}
other => other?,
} Prevention
- Ensure the top level of map fields is always {"...": ...}
- Convert lists to keyed objects before assigning to profile fields
- Validate with is_object() in form validation before save
When it happens
Trigger: Setting a profile JSON-map field (model_windows etc.) to valid JSON that is not an object, e.g. `"[\"deepseek-v4-pro\"]"`, `"\"1M\""`, or `"1000000"`.
Common situations: Users pasting a JSON array of model names, a bare number as the window size, or quoted scalar values instead of a slug→value mapping.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/4b8f1e9fda378298.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/relay_config.rs:2106
let catalog_json = apply_model_metadata_overrides(&catalog_json, &model_metadata)?;
crate::settings::atomic_write(&catalog_path, catalog_json.as_bytes())?;
let mut doc = parse_toml_document(&config_text)?;
doc["model_catalog_json"] = toml_edit::value(catalog_relative);
Ok(normalize_optional_toml(doc))
}
fn parse_model_string_map(
value: &str,
field_name: &str,
) -> anyhow::Result<HashMap<String, String>> {
if value.trim().is_empty() {
return Ok(HashMap::new());
}
let parsed: Value = serde_json::from_str(value)
.map_err(|error| anyhow::anyhow!("{field_name} JSON 解析失败:{error}"))?;
let object = parsed
.as_object()
.ok_or_else(|| anyhow::anyhow!("{field_name} 必须是 JSON 对象"))?;
object
.iter()
.map(|(key, value)| {
let value = value
.as_str()
.ok_or_else(|| anyhow::anyhow!("{field_name} 的模型 {key} 值必须是字符串"))?;
Ok((key.clone(), value.to_string()))
})
.collect()
}
fn validate_model_windows(model_windows: &HashMap<String, String>) -> anyhow::Result<()> {
for (slug, value) in model_windows {
if crate::model_suffix::parse_window_token(value).is_none() {
anyhow::bail!("model_windows 的模型 {slug} 窗口值无效:{value}");
}
}
Ok(())View on GitHub (pinned to b1ed92e5e4)