Skip to content

Commit

Permalink
Add JsonObj api to and from json
Browse files Browse the repository at this point in the history
  • Loading branch information
apiad committed Jan 24, 2018
1 parent 8a6c953 commit 6e8d41c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 31 deletions.
42 changes: 41 additions & 1 deletion docs/jsonobj.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,44 @@ layout: default

# JSON manipulation

`JsonObj` is the swiss-army knife for JSON manipulation. It bridges the world of object oriented programming with the JSON representation.
`JsonObj` is the swiss-army knife for JSON manipulation. It bridges the world of object oriented programming with the JSON representation. You can use a `JsonObj` instance as a regular Python object, setting attribute values, and then obtain a JSON representation easily (you can pass to the `json` method the same args as to the `json.dumps` method):

```python
>>> from jsonapi import JsonObj
>>> obj = JsonObj()
>>> obj.x = 5
>>> obj.y = JsonObj()
>>> obj.y.z = [1,{2:3},None]
>>> obj.json(sort_keys=True)
'{"x": 5, "y": {"z": [1, {"2": 3}, null]}}'

```

The way this works is actually because `JsonObj` can be converted directly to a dictionary represenation, which can be later serialized as JSON (notice how keys are converted to their `str` representation automatically):

```python
>>> from pprint import pprint
>>> pprint(obj.dict())
{'x': 5, 'y': {'z': [1, {'2': 3}, None]}}

```

The opposite also works, i.e., you can create a `JsonObj` from a dictionary and use it with dot-notation for attribute access:

```python
>>> d = {'x': 5, 'y': {'z': [1, {'2': 3}, None]}}
>>> obj = JsonObj(**d)
>>> obj.y.z[1].dict()
{'2': 3}

```

You can also very easily load a JSON file and use it as an object:

```python
>>> s = '{"x": 5, "y": {"z": [1, {"2": 3}, null]}}'
>>> obj = JsonObj.from_json(s)
>>> obj.y.z[1].dict()
{'2': 3}

```
92 changes: 62 additions & 30 deletions jsonapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ def __init__(self, **kwargs):
for k,w in kwargs.items():
setattr(self, str(k), self._parse(w))

def _json(self, x, query):
def dict(self):
return {key: self._json(value) for key, value in self.__dict__.items() if not hasattr(value, '__call__')}

def json(self, **kwargs):
return json.dumps(self.dict(), **kwargs)

def _json(self, x):
if x is None:
return None
if isinstance(x, (int, float, str, bool)):
return x
if isinstance(x, (list, tuple)):
return [self._json(i, query) for i in x]
return [self._json(i) for i in x]
if isinstance(x, dict):
return { str(k): self._json(v) for k,v in x.items() }
if isinstance(x, JsonObj):
return x._query(query)
return x.dict()

raise TypeError("type %s is not supported" % type(x))

def _parse(self, x):
Expand All @@ -30,49 +37,74 @@ def _parse(self, x):
return [self._parse(i) for i in x]
if isinstance(x, dict):
return JsonObj(**x)

raise TypeError("type %s is not supported" % type(x))

def _query(self, query):
if query is None:
return {key: value for key, value in self.__dict__.items() if not hasattr(value, '__call__')}
def __str__(self, **kw):
return json.dumps(self.dict(), **kw)

payload = {}
@staticmethod
def from_json(s):
return JsonObj(**json.loads(s))

if isinstance(query, dict):
items = query.items()
else:
items = [(key, None) for key in query]

for key, value in items:
attr = getattr(self, key)
args = {}
navigation = value
class JsonApi(JsonObj):
def _query(self, obj, query):
if obj is None:
return None

if isinstance(navigation, dict):
if "$" in navigation:
args = { str(k): self._parse(v) for k,v in navigation.pop("$").items() }
else:
for a in list(navigation.keys()):
if a.startswith("$"):
v = navigation.pop(a)
args[a.strip("$")] = self._parse(v)
if isinstance(obj, (int, float, str, bool)):
return obj

if isinstance(obj, (list, tuple)):
return [self._query(i, query) for i in obj]

if hasattr(attr, '__call__'):
result = attr(**args)
if isinstance(obj, dict):
return {str(k): self._query(v, query) for k, v in obj.items()}

if isinstance(obj, JsonObj):
if query is None:
return {key: value for key, value in obj.__dict__.items() if not hasattr(value, '__call__')}

payload = {}

if isinstance(query, dict):
items = query.items()
else:
result = attr
items = [(key, None) for key in query]

for key, value in items:
attr = getattr(obj, key)
args = {}
navigation = value

if isinstance(navigation, dict):
if "$" in navigation:
args = {str(k): self._parse(v)
for k, v in navigation.pop("$").items()}
else:
for a in list(navigation.keys()):
if a.startswith("$"):
v = navigation.pop(a)
args[a.strip("$")] = self._parse(v)

if hasattr(attr, '__call__'):
result = attr(**args)
else:
result = attr

payload[key] = self._json(result, navigation)
payload[key] = self._query(result, navigation)

return payload
return payload

raise TypeError("type %s is not supported" % type(x))


class JsonApi(JsonObj):
def __call__(self, query, encode=False, **kw):
if isinstance(query, str):
query = json.loads(query)

result = self._query(query)
result = self._query(self, query)

if encode:
return json.dumps(result, **kw)
Expand Down

0 comments on commit 6e8d41c

Please sign in to comment.