I have a class A where it stores a collection of variables of type B, how can I serialize class A to JSON properly?
example:
class A:
def __init__(self):
self.b_collection = []
#...
class B:
def __init__(self):
# ...
pass
and add instances of B into the collection:
a = A()
a.b_collection = [B(), B(), B()]
when I try to serialize a with json.dumps(a) I get this error: Object of type A is not JSON serializable.
Is there a way to specify how to encoder should encode that class?
something like
def __encode__(self, encoder):
encoder.start_obj()
encoder.add_property('name', self.value)
encoder.add_property('age', self.age)
encoder.end_obj()
which would return something like
{
name: 'Tomer',
age: '19'
}
You can extend json.JSONEncoder to define how to serialize your objects. The default method of your subclass will take a Python object as an argument. You can return a new object that is (hopefully) encodable, or pass the object on to the parent in hopes that it knows how to encode the object.
For example,
class A:
def __init__(self):
self.b_collection = []
class B:
def __init__(self, name, age):
self.name = name
self.age = age
class ABEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, A):
return {'__A__': obj.b_collection}
elif isinstance(obj, B):
return {'__B__': obj.__dict__}
return super().default(obj)
a = A()
a.b_collection.append(B("Tomer", "19"))
a.b_collection.append(B("Bob", "21"))
a.b_collection.append(B("Alice", "23"))
print(json.dumps(a, cls=ABEncoder, indent=4))
would produce
{
"__A__": [
{
"__B__": {
"name": "Tomer",
"age": "19"
}
},
{
"__B__": {
"name": "Bob",
"age": "21"
}
},
{
"__B__": {
"name": "Alice",
"age": "23"
}
}
]
}
Note that you can handle A and B separately; you don't have to first encode the B objects before returning the encodable form of A; the B objects will be encoded later when the list itself is encoded.
The extra objects make it easier to write a decoder; you don't have to make it this complicated if you don't want to be able to decode the JSON to an instance of A. Instead, you can just define
class ABEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, A):
return obj.b_collection
elif isinstance(obj, B):
return obj.__dict__
return super().default(obj)
to get
[
{
"name": "Tomer",
"age": "19"
},
{
"name": "Bob",
"age": "21"
},
{
"name": "Alice",
"age": "23"
}
]
Maybe data classes can help:
from dataclasses import asdict, dataclass
from typing import List
import json
#dataclass
class Person:
name: str
age: str
#dataclass
class Group:
persons: List[Person]
data = [
{'name': 'Tomer', 'age': '19'},
{'name': 'Ivan', 'age': '20'}
]
persons = [Person(**person) for person in data]
group = Group(persons=persons)
assert json.dumps(asdict(group)) == '{"persons": [{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]}'
persons = [asdict(person) for person in group.persons]
assert persons == [{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]
assert json.dumps(persons) == '[{"name": "Tomer", "age": "19"}, {"name": "Ivan", "age": "20"}]'
Related
I am writing unit test cases for my fastapi project and unable to mock a dynamodb call.
File_1
This file has all the methods to perform DynamoDB actions using boto3 calls.
class DynamoDBRepository:
Insert Item - Inserts value
Get Item - Returns value
#File_2
Has a "AppConfig" class which will be used as a dependency in a later file
from file_1 import DynamoDBRepository
class AppConfig:
def __init__(self) -> None:
"""Constructor class to instantiate dynamodb"""
self._job_table = "Dynamo_DB_Table"
self._region = "Table_region"
self._dynamodb_repository = DynamoDBRepository(table=self._job_table, region=self._region) # created a object for the dynamodb class mentioned in file 1.
File_3:
This file has the fast_api route decorator
from file_2 import AppConfig
#router.get(
"/object/{object_id}"
)
def get_request(
object_id: str,
objects: AppConfig = Depends(AppConfig),
) -> ObjectBody:
try:
object_detail = objects._dynamodb_repository.get_item({"object_id": object_id})
return object_detail["Item"]
I am trying to mock the get_item method in my test file:
File_4
This is my test file in which
client = TestClient(fast_api_app)
class MockAppConfig:
def __init__(self) -> None:
"""Constructor class to instantiate dynamodb and lambda"""
self._job_table = "Dynamo_DB_Table"
self._region = "Table_region"
self._dynamodb_repository = DynamoDBRepository(table=self._job_table, region=self._region)
def test_get_request():
fast_api_app.dependency_overrides[AppConfig] = MockAppConfig
MockAppConfig()._dynamodb_repository.get_item = {
"id": "1234",
"title": "Foo",
"description": "Hello",
}
response = client.get("/objects/1234")
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
The mocking of get_item is not working and it is still querying the original db and failing due to credential check.
I tried monkeypatch & fastapi_dep fixtures, also patching but somehow the get_item method mocking isn't working
Will mocking get_item method work?
class MockDynamoDBRepository():
def get_item(*args, **kwargs):
return {
"Item": {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
}
class MockAppConfig:
def __init__(self) -> None:
"""Constructor class to instantiate dynamodb and lambda"""
self._job_table = "Dynamo_DB_Table"
self._region = "Table_region"
self._dynamodb_repository = MockDynamoDBRepository(table=self._job_table, region=self._region)
def test_get_request():
fast_api_app.dependency_overrides[AppConfig] = MockAppConfig
response = client.get("/objects/1234")
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
Building on #svfat's answer, here is how you can do the test with fastapi_dep - pick any one of the test approaches - with clause or indirect parameter:
class MockDynamoDBRepository():
def __init__(self, *args, **kwargs):
pass
def get_item(self, *args, **kwargs):
return {
"Item": {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
}
class MockAppConfig:
def __init__(self) -> None:
"""Constructor class to instantiate dynamodb and lambda"""
self._job_table = "Mock Dynamo_DB_Table"
self._region = "Mock Table_region"
self._dynamodb_repository = MockDynamoDBRepository(table=self._job_table,
region=self._region)
def test_get_request_deps(fastapi_dep):
with fastapi_dep(fast_api_app).override(
{
AppConfig: MockAppConfig,
}
):
response = client.get("/objects/1234")
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
#pytest.mark.parametrize(
"fastapi_dep",
[
(
fast_api_app,
{AppConfig: MockAppConfig},
)
],
indirect=True,
)
def test_get_request_deps_indirect(fastapi_dep):
response = client.get("/objects/1234")
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
If you don't want to create all the extra classes, you can use the pure mock approach like so:
from mock.mock import MagicMock
def test_get_request_deps_mock(fastapi_dep):
my_mock = MagicMock()
my_mock._dynamodb_repository.get_item.return_value = {
"Item": {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
}
with fastapi_dep(file_3.app).override(
{
AppConfig: lambda: my_mock,
}
):
response = client.get("/objects/1234")
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "Hi",
}
i want to create an object of My_Class and want to tell it with a string what dictionary to get. Is there a better way than to do it with if?
object_dictionary = {
"car" : "some_car",
"house" : "some_house",
"animal" : "some_animal"
}
class My_Class:
def __init__(self, string):
if string == "object_dictionary":
self.dictionary = object_dictionary
obj = My_Class("object_dictionary")
If I want to choose between more dictionaries this will be inconvenient. How can I do this?
object_dictionary = {
"car" : "some_car",
"house" : "some_house",
"animal" : "some_animal"
}
class My_Class:
def __init__(self, string):
self.dictionary = string
obj = My_Class("object_dictionary")
Use dict of dicts. See below
dicts = {
"this_dictionary": {
"car": "some_car",
"house": "some_house",
"animal": "some_animal"
},
"that_dictionary": {
"12": "ttt"
}
}
class MyClass:
def __init__(self, dict_name):
self.dictionary = dicts.get(dict_name, {})
obj = MyClass("that_dictionary")
How can I get JSON from nested python object
class simplobj:
def _init_(self,fname,lname,depart):
self.fname=fname
self.lname=lname
self.depart = depart
class jsonobj:
array = []
def _init_(self,time,listOfSimpleObj):
self.time = time
self.array = listofSimpleObj
class Main:
listOfSimpleObj =[]
sobj = simplobj("fname1","lname1","depart1")
listOfSimpleObj.append(sobj)
outputjsonobj = jsonobj(time,listOfSimpleObj)
output = json.dumps(outputjsonobj._dict_)
output: { "time" : "time", "array": [
{ "fname": "fname1", "lname": "lname1", "depart": "depart1"},
{ "fname": "fname2", "lname": "lname2", "depart": "depart2"},
{ "fname": "fname3", "lname": "lname3", "depart": "depart3"} ] }
You could create a json.JSONEncoder to specify how your classes should be serialized:
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, simplobj) or isinstance(obj, jsonobj):
return obj.__dict__
return json.JSONEncoder.default(self, obj)
...
output = json.dumps(outputjsonobj, cls=MyEncoder)
I'm trying to figure out what would be the best way to create classes in a dynamic manner based on the contents of a JSON file. So for example, here's a snippet from the JSON file:
{
"stuff": [{
"name": "burger",
"aka": ["cheeseburger", "hamburger"]
},
{
"name": "fries",
"aka": ["french fries", "potatoes"]
},
{
"name": "meal",
"items": [{
"name": "burger",
"value": "<burger>"
},
{
"name": "fries",
"value": "<fries>"
}
]
}
]
}
And now based on this JSON, I want classes that represent these objects. So for example, something like:
class Burger:
def __init__(self):
self.name = "burger"
self.aka = ["cheeseburger", "hamburger"]
class Meal:
def __init__(self):
self.name = "meal"
self.burger = Burger()
self.fries = Fries()
So basically, based on that JSON, I want to be able to create classes that represent the same attributes and relationships that we see in the JSON. Any ideas about the best way to approach this would be appreciated!
Assuming json variable contains your json data try this:
for d in json:
name = d.pop('name')
t = type(name, (object,), d)
What it does is to call type, which will create new type in python (exactly the same as if you did class name, which correct name set to content of name variable, with base class object and attributes in d. Variable t will contain class object you want.
I got a class with arrays of classes as properties
class classToJson():
def __init__(self, name, image, objects1, objects2):
self.name = name
self.image = image
self.boolean = True
self.objects1 = objects1
self.objects2 = objects2
def __repr__(self):
return json.dumps(self.__dict__)
object1 and object2 class looks like this:
class object1():
value1 = 1
value2 = 0
class objects2:
def __init__(self, name, value):
self.name = name
self.value = value
This is how I create my json of the class.
obj2 = [...]
parsedObject = classToJson(name, image, [object1], obj2)
file = open("{}.json".format(name),"w")
file.write("[{}]".format(parsedObject.__repr__()))
file.close()
This works if I only use name, image and boolean in the class but when I include objects1 or objects2 I get the TypeError: Object of type 'type' is not JSON serializable. Why?
The json schema I want to accomplish:
[
{
"name": "name",
"image": "image",
"boolean": true,
"objects1": [
{
"value1": 1,
"value2": 0
}
],
"objetcs2": [
{
"name": "name",
"value": "value"
}
]
}
]
Just use the built in json.dump like so
import json
with open('file.json') as f:
json.dump([{
'name': name,
'image': image,
'boolean": True,
'object1': object1,
'object2': object2
}], f)