xtuples.json
1# --------------------------------------------------------------- 2 3import json 4 5from .xtuples import REGISTRY, nTuple, iTuple, Example 6 7# ----- 8 9def cast_json_nTuple(obj): 10 """ 11 >>> ex = Example(1, "a") 12 >>> ex.pipe(cast_json_nTuple) 13 {'x': 1, 's': 'a', 'it': {'__t__': 'iTuple', 'data': []}, '__t__': 'Example'} 14 """ 15 d = { 16 k: cast_json(v) 17 for k, v in obj._asdict().items() 18 # 19 } 20 d["__t__"] = type(obj).__name__ 21 return d 22 23def uncast_json_nTuple(cls, obj): 24 """ 25 >>> ex = Example(1, "a") 26 >>> uncast_json_nTuple(Example, ex.pipe(cast_json_nTuple)) 27 Example(x=1, s='a', it=iTuple()) 28 """ 29 assert obj["__t__"] == cls.__name__ 30 return cls( 31 *( 32 uncast_json(v) 33 for k, v in obj.items() if k != "__t__" 34 ) 35 ) 36 37def cast_json_iTuple(self): 38 """ 39 >>> iTuple.range(1).pipe(cast_json_iTuple) 40 {'__t__': 'iTuple', 'data': [0]} 41 """ 42 return dict( 43 __t__ = type(self).__name__, 44 data = list(self.map(cast_json)), 45 ) 46 47def uncast_json_iTuple(cls, obj): 48 """ 49 >>> uncast_json_iTuple(iTuple, iTuple.range(1).pipe(cast_json_iTuple)) 50 iTuple(0) 51 """ 52 assert obj["__t__"] == cls.__name__ 53 return cls(data=obj["data"]) 54 55 56def cast_json(obj, default = lambda obj: obj): 57 """ 58 >>> ex = Example(1, "a") 59 >>> ex.pipe(cast_json) 60 {'x': 1, 's': 'a', 'it': {'__t__': 'iTuple', 'data': []}, '__t__': 'Example'} 61 >>> iTuple.range(1).pipe(cast_json) 62 {'__t__': 'iTuple', 'data': [0]} 63 """ 64 if nTuple.is_instance(obj): 65 return cast_json_nTuple(obj) 66 elif isinstance(obj, iTuple): 67 return cast_json_iTuple(obj) 68 return default(obj) 69 70def uncast_json(obj): 71 """ 72 >>> ex = Example(1, "a") 73 >>> uncast_json(ex.pipe(cast_json)) 74 Example(x=1, s='a', it=iTuple()) 75 >>> uncast_json(iTuple.range(1).pipe(cast_json)) 76 iTuple(0) 77 """ 78 if not isinstance(obj, dict): 79 return obj 80 __t__ = obj.get("__t__", None) 81 if __t__ is None: 82 return obj 83 cls = iTuple if __t__ == "iTuple" else REGISTRY[__t__] 84 if cls is iTuple or issubclass(cls, iTuple): 85 return uncast_json_iTuple(cls, obj) 86 return uncast_json_nTuple(cls, obj) 87 88# --------------------------------------------------------------- 89 90class JSONEncoder(json.JSONEncoder): 91 92 def iterencode(self, o, *args, **kwargs): 93 for chunk in super().iterencode( 94 cast_json(o), *args, **kwargs 95 ): 96 yield chunk 97 98 # def meta_default(self, obj): 99 # return json.JSONEncoder.default(self, obj) 100 101 # def default(self, obj): 102 # if isinstance(obj, fDict): 103 # return self.meta_default(obj.data) 104 # return cast_json(obj, default=self.meta_default) 105 106# ----- 107 108class JSONDecoder(json.JSONDecoder): 109 110 def __init__(self, *args, **kwargs): 111 json.JSONDecoder.__init__( 112 self, 113 object_hook=self.object_hook, 114 *args, 115 **kwargs 116 # 117 ) 118 119 @classmethod 120 def xtuple_object_hook(cls, d): 121 return uncast_json(d) 122 123 def object_hook(self, d): 124 return self.xtuple_object_hook(d) 125 126# --------------------------------------------------------------- 127 128# TODO: fString so can do .pipe ? 129def to_json(v, **kwargs): 130 """ 131 >>> print(iTuple([Example(1, "a")]).pipe(to_json, indent=2)) 132 { 133 "__t__": "iTuple", 134 "data": [ 135 { 136 "x": 1, 137 "s": "a", 138 "it": { 139 "__t__": "iTuple", 140 "data": [] 141 }, 142 "__t__": "Example" 143 } 144 ] 145 } 146 >>> print(iTuple([ 147 ... iTuple([Example(1, "a")]) 148 ... ]).pipe(to_json, indent=2)) 149 { 150 "__t__": "iTuple", 151 "data": [ 152 { 153 "__t__": "iTuple", 154 "data": [ 155 { 156 "x": 1, 157 "s": "a", 158 "it": { 159 "__t__": "iTuple", 160 "data": [] 161 }, 162 "__t__": "Example" 163 } 164 ] 165 } 166 ] 167 } 168 >>> print(Example(2, "b", iTuple([ 169 ... iTuple([Example(1, "a")]) 170 ... ])).pipe(to_json, indent=2)) 171 { 172 "x": 2, 173 "s": "b", 174 "it": { 175 "__t__": "iTuple", 176 "data": [ 177 { 178 "__t__": "iTuple", 179 "data": [ 180 { 181 "x": 1, 182 "s": "a", 183 "it": { 184 "__t__": "iTuple", 185 "data": [] 186 }, 187 "__t__": "Example" 188 } 189 ] 190 } 191 ] 192 }, 193 "__t__": "Example" 194 } 195 """ 196 return json.dumps(v, cls=JSONEncoder, **kwargs) 197 198def from_json(v: str, **kwargs): 199 """ 200 >>> ex = iTuple([Example(1, "a")]) 201 >>> from_json(ex.pipe(to_json)) 202 iTuple(Example(x=1, s='a', it=iTuple())) 203 >>> from_json( 204 ... iTuple([iTuple([Example(1, "a")])]).pipe(to_json) 205 ... ) 206 iTuple(iTuple(Example(x=1, s='a', it=iTuple()))) 207 >>> from_json( 208 ... Example(2, "b", iTuple([ 209 ... iTuple([Example(1, "a")]) 210 ... ])).pipe(to_json) 211 ... ) 212 Example(x=2, s='b', it=iTuple(iTuple(Example(x=1, s='a', it=iTuple())))) 213 """ 214 return json.loads(v, cls=JSONDecoder, **kwargs) 215 216def load_json(f): 217 return json.load(f, cls=JSONDecoder) 218 219def dump_json(f, v): 220 return json.dump(f, v, cls=JSONEncoder) 221 222# --------------------------------------------------------------- 223 224__all__ = [ 225 "JSONEncoder", 226 "JSONDecoder", 227] 228 229# ---------------------------------------------------------------
92class JSONEncoder(json.JSONEncoder): 93 94 def iterencode(self, o, *args, **kwargs): 95 for chunk in super().iterencode( 96 cast_json(o), *args, **kwargs 97 ): 98 yield chunk
Extensible JSON http://json.org encoder for Python data structures.
Supports the following objects and types by default:
+-------------------+---------------+ | Python | JSON | +===================+===============+ | dict | object | +-------------------+---------------+ | list, tuple | array | +-------------------+---------------+ | str | string | +-------------------+---------------+ | int, float | number | +-------------------+---------------+ | True | true | +-------------------+---------------+ | False | false | +-------------------+---------------+ | None | null | +-------------------+---------------+
To extend this to recognize other objects, subclass and implement a
.default()
method with another method that returns a serializable
object for o
if possible, otherwise it should call the superclass
implementation (to raise TypeError
).
94 def iterencode(self, o, *args, **kwargs): 95 for chunk in super().iterencode( 96 cast_json(o), *args, **kwargs 97 ): 98 yield chunk
Encode the given object and yield each string representation as available.
For example::
for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)
Inherited Members
- json.encoder.JSONEncoder
- JSONEncoder
- default
- encode
110class JSONDecoder(json.JSONDecoder): 111 112 def __init__(self, *args, **kwargs): 113 json.JSONDecoder.__init__( 114 self, 115 object_hook=self.object_hook, 116 *args, 117 **kwargs 118 # 119 ) 120 121 @classmethod 122 def xtuple_object_hook(cls, d): 123 return uncast_json(d) 124 125 def object_hook(self, d): 126 return self.xtuple_object_hook(d)
Simple JSON http://json.org decoder
Performs the following translations in decoding by default:
+---------------+-------------------+ | JSON | Python | +===============+===================+ | object | dict | +---------------+-------------------+ | array | list | +---------------+-------------------+ | string | str | +---------------+-------------------+ | number (int) | int | +---------------+-------------------+ | number (real) | float | +---------------+-------------------+ | true | True | +---------------+-------------------+ | false | False | +---------------+-------------------+ | null | None | +---------------+-------------------+
It also understands NaN
, Infinity
, and -Infinity
as
their corresponding float
values, which is outside the JSON spec.
112 def __init__(self, *args, **kwargs): 113 json.JSONDecoder.__init__( 114 self, 115 object_hook=self.object_hook, 116 *args, 117 **kwargs 118 # 119 )
object_hook
, if specified, will be called with the result
of every JSON object decoded and its return value will be used in
place of the given dict
. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
object_pairs_hook
, if specified will be called with the result of
every JSON object decoded with an ordered list of pairs. The return
value of object_pairs_hook
will be used instead of the dict
.
This feature can be used to implement custom decoders.
If object_hook
is also defined, the object_pairs_hook
takes
priority.
parse_float
, if specified, will be called with the string
of every JSON float to be decoded. By default this is equivalent to
float(num_str). This can be used to use another datatype or parser
for JSON floats (e.g. decimal.Decimal).
parse_int
, if specified, will be called with the string
of every JSON int to be decoded. By default this is equivalent to
int(num_str). This can be used to use another datatype or parser
for JSON integers (e.g. float).
parse_constant
, if specified, will be called with one of the
following strings: -Infinity, Infinity, NaN.
This can be used to raise an exception if invalid JSON numbers
are encountered.
If strict
is false (true is the default), then control
characters will be allowed inside strings. Control characters in
this context are those with character codes in the 0-31 range,
including '\t'
(tab), '\n'
, '\r'
and '\0'
.
Inherited Members
- json.decoder.JSONDecoder
- decode
- raw_decode