{"record":{"id":"b6594a60475deaf7","repo":"docling-project/docling","slug":"invalid-json-for-nested-config-field-exc","errorCode":null,"errorMessage":"Invalid JSON for nested config field: {exc}","messagePattern":"Invalid JSON for nested config field: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/datamodel/service/options.py","lineNumber":900,"sourceCode":"        \"table_structure_custom_config\",\n        \"layout_custom_config\",\n        \"picture_classification_custom_config\",\n        mode=\"before\",\n    )\n    @classmethod\n    def _decode_json_string_config(cls, value: Any) -> Any:\n        \"\"\"Accept a JSON-encoded string for nested config fields.\n\n        Local-file conversions submit options as ``multipart/form-data``, where\n        nested configs are sent as JSON strings because form fields cannot carry\n        objects. Decode them back into dicts here. Values that already arrive as\n        objects (e.g. via JSON request bodies) are returned unchanged.\n        \"\"\"\n        if isinstance(value, str):\n            try:\n                return json.loads(value)\n            except json.JSONDecodeError as exc:\n                raise ValueError(\n                    f\"Invalid JSON for nested config field: {exc}\"\n                ) from exc\n        return value\n\n    # Field validators for deprecated fields - trigger warnings on assignment\n    @field_validator(\"picture_description_api\", mode=\"before\")\n    @classmethod\n    def validate_picture_description_api(cls, v):\n        \"\"\"Emit deprecation warning when picture_description_api is set.\"\"\"\n        if v is not None:\n            warnings.warn(\n                \"picture_description_api is deprecated. \"\n                \"Please migrate to picture_description_preset or \"\n                \"picture_description_custom_config.\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n        return v","sourceCodeStart":882,"sourceCodeEnd":918,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/datamodel/service/options.py#L882-L918","documentation":"Field validator (mode='before') on the docling service options: when a nested configuration field arrives as a string, it is parsed with json.loads; if parsing fails, this ValueError is raised with the underlying JSONDecodeError detail. It exists because multipart/form-data submissions (local-file conversions) must serialize nested configs as JSON strings, and malformed strings are caught here rather than deep inside Pydantic.","triggerScenarios":"POSTing to the service with multipart/form-data where a nested field (e.g., pipeline options or a nested config object) contains invalid JSON — missing quotes, trailing commas, single quotes, unescaped newlines. Does not fire for JSON request bodies, which arrive as real objects.","commonSituations":"Hand-built curl multipart forms; client code that str()-dumps a Python dict into a form field instead of json.dumps; form values truncated by proxies or shell quoting issues.","solutions":["Ensure every nested config sent as a form field is serialized with json.dumps (never str(dict)).","Validate the string with json.loads client-side before sending.","Prefer the JSON request body endpoint when sending complex nested options.","Check the exc detail in the message for the exact character position of the JSON syntax error."],"exampleFix":"# before\nrequests.post(url, files=files, data={'pipeline_options': str(options_dict)})  # single quotes -> invalid JSON\n\n# after\nimport json\nrequests.post(url, files=files, data={'pipeline_options': json.dumps(options_dict)})","handlingStrategy":"validation","validationCode":"import json\n\ndef valid_nested_config_strings(payload: dict) -> bool:\n    nested = ('pipeline_options',)  # keys your client sends as form strings\n    return all(\n        not isinstance(v, str) or _is_json(v)\n        for k, v in payload.items() if k in nested\n    )\n\ndef _is_json(s: str) -> bool:\n    try:\n        json.loads(s)\n        return True\n    except json.JSONDecodeError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(url, files=files, data=form)\nexcept ValueError:\n    # client-side: validate form strings with json.loads before sending\n    pass","preventionTips":["Always serialize nested form fields with json.dumps, never str(dict).","Round-trip check: json.loads(json.dumps(obj)) before submission.","Use the JSON request body endpoint for complex nested options."],"tags":["service","json","http","form-data","validation"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}