{"record":{"id":"2caa5211d474bace","repo":"infiniflow/ragflow","slug":"argument-error","errorCode":"ARGUMENT_ERROR","errorMessage":"DSL is not JSON-serializable.","messagePattern":"DSL is not JSON-serializable\\.","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"api/apps/services/canvas_replica_service.py","lineNumber":61,"sourceCode":"    LOCK_RETRY_SLEEP_SECS = 0.2\n\n    @classmethod\n    def normalize_dsl(cls, dsl):\n        \"\"\"Normalize DSL to a JSON-serializable dict. Raise ValueError on invalid input.\"\"\"\n        normalized = dsl\n        if isinstance(normalized, str):\n            try:\n                normalized = json.loads(normalized)\n            except Exception as e:\n                raise ValueError(\"Invalid DSL JSON string.\") from e\n\n        if not isinstance(normalized, dict):\n            raise ValueError(\"DSL must be a JSON object.\")\n\n        try:\n            return json.loads(json.dumps(normalize_chunker_dsl(normalized), ensure_ascii=False))\n        except Exception as e:\n            raise ValueError(\"DSL is not JSON-serializable.\") from e\n\n    @classmethod\n    def _replica_key(cls, canvas_id: str, tenant_id: str, runtime_user_id: str) -> str:\n        return f\"{cls.REPLICA_KEY_PREFIX}:{canvas_id}:{tenant_id}:{runtime_user_id}\"\n\n    @classmethod\n    def _lock_key(cls, canvas_id: str, tenant_id: str, runtime_user_id: str) -> str:\n        return f\"{cls.LOCK_KEY_PREFIX}:{canvas_id}:{tenant_id}:{runtime_user_id}\"\n\n    @classmethod\n    def _read_payload(cls, replica_key: str):\n        \"\"\"Read replica payload from Redis; return None on missing/invalid content.\"\"\"\n        cache_blob = REDIS_CONN.get(replica_key)\n        if not cache_blob:\n            return None\n        try:\n            payload = json.loads(cache_blob)\n            if not isinstance(payload, dict):","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/apps/services/canvas_replica_service.py#L43-L79","documentation":"Raised by CanvasReplicaService.normalize_dsl when normalize_chunker_dsl() produces a structure that json.dumps cannot serialize (raising inside the dumps/loads round-trip). This catches DSL objects containing non-JSON values — datetime objects, sets, custom class instances, NaN/Infinity floats — that would otherwise poison the Redis replica payload.","triggerScenarios":"Submitting a canvas DSL built programmatically where component params contain Python-specific objects (set, datetime, Decimal, numpy types, float('nan')); a migration path in normalize_chunker_dsl that emits such a value; NaN floats, which json.dumps allows by default but json.loads(strict) round-trip can reject.","commonSituations":"Building DSL from ORM rows or API responses without converting datetimes to ISO strings; pandas/numpy values leaking into params; old DSL files with Infinity/NaN literals produced by allow_nan=True serializers.","solutions":["Sanitize the DSL tree before submission: convert datetime -> isoformat, set -> list, Decimal -> float/str, NaN/Inf -> None","Round-trip locally with json.loads(json.dumps(dsl, allow_nan=False)) to reproduce exactly what the server rejects","Add a custom default= handler to any json.dumps used to build the DSL","If NaNs come from numpy, cast with float() and replace non-finite values before serializing"],"exampleFix":"# before\ndsl[\"created_at\"] = row.created_at          # datetime object\ndsl[\"ids\"] = {\"a\", \"b\"}                    # set\n\n# after\ndsl[\"created_at\"] = row.created_at.isoformat()\ndsl[\"ids\"] = sorted({\"a\", \"b\"})\njson.dumps(dsl, allow_nan=False, default=str)","handlingStrategy":"validation","validationCode":"import json, math\n\ndef json_safe(dsl):\n    json.loads(json.dumps(dsl, allow_nan=False, default=_reject))\n    return dsl\ndef _reject(o):\n    raise TypeError(f\"non-JSON value: {o!r}\")","typeGuard":"def is_json_serializable(v) -> bool:\n    try:\n        json.dumps(v, allow_nan=False)\n        return True\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    service.normalize_dsl(dsl)\nexcept ValueError as e:\n    if \"serializable\" in str(e):\n        dsl = sanitize(dsl)  # datetimes->isoformat, sets->list, NaN->None\n        service.normalize_dsl(dsl)","preventionTips":["Convert datetime/Decimal/set/numpy values at the boundary where DSL is built","Use allow_nan=False in every dumps so bad floats fail locally first","Never insert ORM model instances into DSL params; extract primitives"],"tags":["canvas","dsl","serialization","validation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}