11

I need to convert a json-string to python object. By object I mean "new" python3 object like:

class MyClass(object):

I found several help for example on jsonpickle documentation. But all I found are tutorials which convert object to json first and after this convert backwards.

I want to convert a json-string from a Rest-API.

Here is what I have done so far:

import requests
import jsonpickle

class Goal(object):
    def __init__(self):
        self.GoaldID = -1
        self.IsPenalty = False

class Match(object):
    def __init__(self):
        self.Goals = []

headers = {
    "Content-Type": "application/json; charset=utf-8"
}

url = "https://www.openligadb.de/api/getmatchdata/39738"

result = requests.get(url=url, headers=headers)
obj = jsonpickle.decode(result.json)
print (obj)

This results in:

TypeError: the JSON object must be str, bytes or bytearray, not 'method'

It's quite clear to me that jsonpickle can't convert this to my classes (Goal, Match), because I don't tell jsonpickle in which class the output should be converted. The problem is I don't know how to tell jsonpickle to convert the JSON in object from type Match? And how can I tell that the list of goals should be of type List<Goal>?

falsetru
  • 357,413
  • 63
  • 732
  • 636
Sebi
  • 3,879
  • 2
  • 35
  • 62

3 Answers3

9

The following lines will give you a dictionary:

obj = jsonpickle.decode(result.content)  # NOTE: `.content`, not `.json`

obj = result.json()

But none of above will give you what you want (python object (not dicitonary)). because the json from the url is not encoded with jsonpickle.encode - whcih add additional information to a generated json (something like {"py/object": "__main__.Goal", ....})


>>> import jsonpickle
>>> class Goal(object):
...     def __init__(self):
...         self.GoaldID = -1
...         self.IsPenalty = False
...
>>> jsonpickle.encode(Goal())
'{"py/object": "__main__.Goal", "IsPenalty": false, "GoaldID": -1}'
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# JSON encoded with jsonpickle.encode  (default unpicklable=True)
#   => additional python class information attached
#   => can be decoded back to Python object
>>> jsonpickle.decode(jsonpickle.encode(Goal()))
<__main__.Goal object at 0x10af0e510>


>>> jsonpickle.encode(Goal(), unpicklable=False)
'{"IsPenalty": false, "GoaldID": -1}'
# with unpicklable=False   (similar output with json.dumps(..))
#   => no python class information attached
#   => cannot be decoded back to Python object, but a dict
>>> jsonpickle.decode(jsonpickle.encode(Goal(), unpicklable=False))
{'IsPenalty': False, 'GoaldID': -1}

If you want an actual Python object which is not a dictionary, i.e. you prefer dic.Goals.[0].GoalGetterName to dic["Goals"][0]["GoalGetterName"], use json.loads with object_hook:

import json
import types    
import requests

url = "https://www.openligadb.de/api/getmatchdata/39738"

result = requests.get(url)
data = json.loads(result.content, object_hook=lambda d: types.SimpleNamespace(**d))
# OR   data = result.json(object_hook=lambda d: types.SimpleNamespace(**d))
goal_getter = data.Goals[0].GoalGetterName
# You get `types.SimpleNamespace` objects in place of dictionaries
Amit Kotlovski
  • 1,848
  • 1
  • 18
  • 13
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • Ok thank you very much. Because I don't like the magic string access via dic, is it best practice to use a class method which converts a dic to an object? – Sebi Mar 08 '17 at 13:10
  • @Sebi, Why do you convert the dict to an object? I just used the dictionary/list/... without converting when I was using Python. (I don't think it's a magic string access ;;;) – falsetru Mar 08 '17 at 13:12
  • @Sebi, I didn't use this, but check this out: http://jsonmodels.readthedocs.io/en/latest/ – falsetru Mar 08 '17 at 13:13
  • It's because I just don't like the magic string access, for example: dic["Goals"][0]["GoalGetterName"], I would prefer match.goals[0].goalGetterName but maybe this needs to much afford. – Sebi Mar 08 '17 at 13:14
  • @Sebi, if you don't want to define mapping, you can define custom [`JSONDecoder`](https://docs.python.org/3/library/json.html#json.JSONDecoder) which convert JSON object to [`types.SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) instead of Python dictionary. – falsetru Mar 08 '17 at 13:17
  • @Sebi, BTW, `dic["Goals"][0]["GoalGetterName"]` has no magic. Just simple item accesses ;) – falsetru Mar 08 '17 at 13:18
  • Thanks for all the stuff :) Now I will find a way. JSONModels look nice. What I mean by magic string is a hardcoded string like "Goals", which I don't because it isn't able to refactor this and so on...But I'm from C# maybe I'm just not familiar enough with this dynamic stuff ;) – Sebi Mar 08 '17 at 13:22
  • @Sebi, Maybe using `json.loads` with `object_hook` is more appropriate for your case. – falsetru Mar 08 '17 at 13:24
  • @Sebi, I updated the answer to include an example with `json.loads(..., object_hook=...)` – falsetru Mar 08 '17 at 13:28
  • Thank you very much. Would give +2 if possible :-D. This helped me alot. – Sebi Mar 08 '17 at 13:47
  • @Sebi, No problem. Happy Python hacking! – falsetru Mar 08 '17 at 13:48
7

Do you mean something like this?

import json

class JsonObject(object):   

    def __init__(self, json_content):
        data = json.loads(json_content)
        for key, value in data.items():
            self.__dict__[key] = value      


jo = JsonObject("{\"key1\":1234,\"key2\":\"Hello World\"}")
print(jo.key1)

which prints:

1234
[Finished in 0.4s]
Den1al
  • 617
  • 1
  • 10
  • 16
1

A clean approach on recent python versions might be to use marshmallow-dataclass :

from dataclasses import field
from marshmallow_dataclass import dataclass 
from typing import List

@dataclass
class Goal:
    GoaldID: int = field(default=-1)
    IsPenalty: bool = field(default=False)


@dataclass
class Match:
    Goals: List[Goal] = field(default_factory=lambda: [])

my_match, _ = Match.Schema().load(result.json())
lovasoa
  • 6,419
  • 1
  • 35
  • 45