{"record":{"id":"18db6e3a1e0d7636","repo":"sgl-project/sglang","slug":"field-name-is-not-valid-json","errorCode":null,"errorMessage":"{field_name} is not valid JSON","messagePattern":"(.+?) is not valid JSON","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/entrypoints/action/api.py","lineNumber":96,"sourceCode":"\ndef _parse_form_value(value: Any) -> Any:\n    if not isinstance(value, str):\n        return value\n    if not value.strip():\n        return None\n    try:\n        return json.loads(value)\n    except Exception:\n        return value\n\n\ndef _parse_extra_params(value: Any, field_name: str) -> dict[str, Any]:\n    if value in (None, \"\"):\n        return {}\n    try:\n        parsed = json.loads(value) if isinstance(value, str) else value\n    except (json.JSONDecodeError, TypeError) as exc:\n        raise ValueError(f\"{field_name} is not valid JSON\") from exc\n    if not isinstance(parsed, dict):\n        raise ValueError(f\"{field_name} must be a JSON object\")\n    return flatten_extra_params(dict(parsed))\n\n\ndef _is_form_upload(value: Any) -> bool:\n    return callable(getattr(value, \"read\", None)) and hasattr(value, \"filename\")\n\n\ndef _is_probably_video_upload(value: Any) -> bool:\n    content_type = (getattr(value, \"content_type\", \"\") or \"\").lower()\n    if content_type.startswith(\"video/\"):\n        return True\n    filename = getattr(value, \"filename\", None)\n    if not filename:\n        return False\n    filename = str(filename).split(\"?\", 1)[0].split(\"#\", 1)[0]\n    return os.path.splitext(filename)[1].lower() in _ACTION_VIDEO_EXTENSIONS","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/entrypoints/action/api.py#L78-L114","documentation":"_parse_extra_params parses string fields (e.g. extra_params in a multipart form) as JSON; a malformed JSON string raises ValueError('{field_name} is not valid JSON'), returned as HTTP 400. Non-JSON-serializable non-string types also trigger it via TypeError.","triggerScenarios":"Submitting a multipart action request where extra_params='{\"top_k\": ' (truncated) or 'top_k=5' (URL-encoded form syntax instead of JSON), or passing an unserializable object.","commonSituations":"Using form-style key=value strings where a JSON object is expected; smart quotes or trailing commas from copy-pasting; truncated request bodies.","solutions":["Fix the field to be a valid JSON object string, e.g. '{\"top_k\": 5}'","Do not use query-string/form encoding (a=1&b=2) in this field; JSON-encode a dict instead","Send the request as application/json body rather than multipart if possible, where params are already structured"],"exampleFix":"# before\nextra_params=top_k=5;temperature=0.7\n\n# after\nextra_params={\"top_k\": 5, \"temperature\": 0.7}","handlingStrategy":"validation","validationCode":"import json\ndef safe_extra_params(v):\n    if v in (None, \"\"):\n        return {}\n    parsed = json.loads(v)\n    assert isinstance(parsed, dict)\n    return parsed  # send json.dumps(parsed) in the form field","typeGuard":null,"tryCatchPattern":"try:\n    resp = client.post(url, files=files)\n    resp.raise_for_status()\nexcept HTTPError:\n    if \"not valid JSON\" in resp.text: fix_extra_params_string()\n    else: raise","preventionTips":["Always build extra_params with json.dumps(dict), never hand-write the string","Send structured JSON bodies instead of multipart when the client supports it","Lint form fields in client integration tests"],"tags":["api","json","request-validation","multipart"],"backgroundTag":"invalid-json-in-request","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}