{"record":{"id":"c211a7ced2414a31","repo":"HumanSignal/label-studio","slug":"annotation-result-can-t-be-parse-from-str-to-jso","errorCode":null,"errorMessage":"annotation \"result\" can't be parse from str to JSON","messagePattern":"annotation \"result\" can't be parse from str to JSON","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"label_studio/tasks/serializers.py","lineNumber":198,"sourceCode":"        try:\n            return super().create(*args, **kwargs)\n        except IntegrityError as e:\n            errors = [\n                'UNIQUE constraint failed: task_completion.unique_id',\n                'duplicate key value violates unique constraint \"task_completion_unique_id_key\"',\n            ]\n            if any([error in str(e) for error in errors]):\n                raise AnnotationDuplicateError()\n            raise\n\n    def validate_result(self, value):\n        data = value\n        # convert from str to json if need\n        if isinstance(value, str):\n            try:\n                data = json.loads(value)\n            except:  # noqa: E722\n                raise ValueError('annotation \"result\" can\\'t be parse from str to JSON')\n\n        # check result is list\n        if not isinstance(data, list):\n            raise ValidationError('annotation \"result\" field in annotation must be list')\n\n        # FIT-1669: collapse `(id, from_name, type)` collisions at the write boundary\n        # so the annotation record never persists duplicate-id rows.\n        return dedupe_annotation_result_list(data)\n\n    def _resolve_project_for_validation(self, data):\n        if 'task' in data:\n            return data['task'].project\n        if self.instance is not None:\n            return self.instance.project\n        task = self.context.get('task')\n        if task is not None:\n            return task.project\n        return None","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/tasks/serializers.py#L180-L216","documentation":"AnnotationSerializer.validate_result() accepts the annotation 'result' either as parsed JSON (list) or as a JSON string, which it json.loads()s. If the string cannot be parsed as JSON, ValueError is raised (note: ValueError, not DRF ValidationError, so it surfaces as a 500 unless caught).","triggerScenarios":"POST/PUT of an annotation with result sent as a string that is not valid JSON — e.g. single-quoted JSON ({'a': 1}), Python repr of a dict, trailing commas, or a non-JSON string like \"label-a\" — to the annotations API or via AnnotationSerializer directly.","commonSituations":"Clients building the JSON string with Python str(dict) instead of json.dumps; form-data/text content types where the JSON string is preserved literally; copy-pasted results from logs; double-encoding mistakes.","solutions":["Send result as an actual JSON array in the request body (correct Content-Type: application/json), not as a stringified blob","If sending a string, ensure it is valid JSON: use json.dumps(obj) (double quotes, no trailing commas) — never str(obj) or repr(obj)","Catch ValueError in custom code paths around serializer.is_valid() since this specific failure raises ValueError, not ValidationError","Validate with json.loads(result) client-side before the request"],"exampleFix":"// before\npayload = {\"result\": str(my_result_list)}          # \"[{'id': 1, ...}]\" -> invalid JSON\n// after\npayload = {\"result\": json.dumps(my_result_list)}   # or send the list itself","handlingStrategy":"validation","validationCode":"import json\n\ndef assert_result_string_is_json(value):\n    if isinstance(value, str):\n        try:\n            json.loads(value)\n        except json.JSONDecodeError as e:\n            raise ValueError(f\"result string is not valid JSON: {e}\")","typeGuard":"def is_parsable_json_string(value) -> bool:\n    if not isinstance(value, str):\n        return True\n    try:\n        json.loads(value)\n        return True\n    except json.JSONDecodeError:\n        return False","tryCatchPattern":"try:\n    ser = AnnotationSerializer(data=payload)\n    ser.is_valid(raise_exception=True)\nexcept ValueError as e:\n    # validate_result raises ValueError (not ValidationError) on unparseable strings\n    logger.error(\"annotation result is not valid JSON: %s\", e)\nexcept Exception as e:\n    logger.error(\"annotation rejected: %s\", e)","preventionTips":["Send result as a native JSON array (Content-Type: application/json), not a stringified blob","Serialize with json.dumps, never str()/repr() of Python objects","Validate with json.loads client-side before sending","Catch ValueError separately from ValidationError around serializer.is_valid() since this path raises ValueError"],"tags":["annotation","json","validation","serializer"],"backgroundTag":"invalid-json-payload","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}