Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/json.py: 33%
38 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +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 return utils.json.loads(val)
51 def singleValueFromClient(self, value: str | list | dict, skel, bone_name, client_data):
52 if value:
53 if not isinstance(value, (list, dict)):
54 value = str(value)
56 # Try to parse a JSON string
57 try:
58 value = utils.json.loads(value)
60 except json.decoder.JSONDecodeError as e:
61 # Try to parse a Python dict as fallback
62 try:
63 value = ast.literal_eval(value)
65 except (SyntaxError, ValueError):
66 # If this fails, report back the JSON parse error
67 return self.getEmptyValue(), [
68 ReadFromClientError(ReadFromClientErrorSeverity.Invalid, f"Invalid JSON supplied: {e!s}")
69 ]
71 try:
72 jsonschema.validate(value, self.schema)
73 except (jsonschema.exceptions.ValidationError, jsonschema.exceptions.SchemaError) as e:
74 return self.getEmptyValue(), [
75 ReadFromClientError(
76 ReadFromClientErrorSeverity.Invalid,
77 f"Invalid JSON for schema supplied: {e!s}")
78 ]
80 return super().singleValueFromClient(value, skel, bone_name, client_data)
82 def structure(self) -> dict:
83 return super().structure() | {
84 "schema": self.schema
85 }