Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/json.py: 30%
40 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-07 19:28 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-07 19:28 +0000
1import ast
2import json
3import jsonschema
4import typing as t
5from viur.core.bones.base import ReadFromClientError, ReadFromClientErrorSeverity
6from viur.core.bones.raw import RawBone
7from viur.core import utils
10class JsonBone(RawBone):
11 """
12 This bone saves its content as a JSON-string, but unpacks its content to a dict or list when used.
13 :param schema If provided we can control and verify which data to accept.
15 .. code-block:: python
17 # Example
18 schema= {"type": "object", "properties" :{"price": {"type": "number"},"name": {"type": "string"}}
19 # This will only accept the provided JSON when price is a number and name is a string.
21 """
23 type = "raw.json"
25 def __init__(
26 self,
27 *,
28 indexed: bool = False,
29 multiple: bool = False,
30 languages: bool = None,
31 schema: t.Mapping = {},
32 **kwargs
33 ):
34 # JsonBone is bound to certain limits
35 assert not multiple
36 assert not languages
37 assert not indexed
39 super().__init__(indexed=indexed, multiple=multiple, languages=languages, **kwargs)
41 # Validate the schema; if it's invalid a SchemaError will be raised
42 jsonschema.validators.validator_for(False).check_schema(schema)
43 self.schema = schema
45 def singleValueSerialize(self, value, skel: 'SkeletonInstance', name: str, parentIndexed: bool):
46 return utils.json.dumps(skel.accessedValues[name])
48 def singleValueUnserialize(self, val):
49 if isinstance(val, (str, bytes, bytearray)):
50 return utils.json.loads(val)
51 return val
53 def singleValueFromClient(self, value: str | list | dict, skel, bone_name, client_data):
54 if value:
55 if not isinstance(value, (list, dict)):
56 value = str(value)
58 # Try to parse a JSON string
59 try:
60 value = utils.json.loads(value)
62 except json.decoder.JSONDecodeError as e:
63 # Try to parse a Python dict as fallback
64 try:
65 value = ast.literal_eval(value)
67 except (SyntaxError, ValueError):
68 # If this fails, report back the JSON parse error
69 return self.getEmptyValue(), [
70 ReadFromClientError(ReadFromClientErrorSeverity.Invalid, f"Invalid JSON supplied: {e!s}")
71 ]
73 try:
74 jsonschema.validate(value, self.schema)
75 except (jsonschema.exceptions.ValidationError, jsonschema.exceptions.SchemaError) as e:
76 return self.getEmptyValue(), [
77 ReadFromClientError(
78 ReadFromClientErrorSeverity.Invalid,
79 f"Invalid JSON for schema supplied: {e!s}")
80 ]
82 return super().singleValueFromClient(value, skel, bone_name, client_data)
84 def structure(self) -> dict:
85 return super().structure() | {
86 "schema": self.schema
87 }