I have two ways to represent Python object by json.dumps()
First:
person = {
"name": "John",
"age": 30,
"city": "New York"
}
Second:
class Person:
def _init_(self, name, age, city):
self.name = name
self.age = age
self.city = city
person = Person("John", 30, "New York")
Then I tried p1 = json.dumps(person), the second way would say it's not JSON serializable.
So basically for Python, json.dumps only work for built-in object like dict object?
If you are asking about vanilla Python, serialization could be done this way:
import json
class Person:
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__)
person = Person("John", 30, "New York")
print(person.to_json())
So we're just converting an object to a dict using __dict__ attribute.
But if you need something more sophisticated, you might need DRF (Django REST Framework) or pydantic. An example how it could be done with DRF:
from rest_framework import serializers
from rest_framework.serializers import Serializer
class Person:
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
class PersonSerializer(Serializer):
name = serializers.CharField()
age = serializers.IntegerField()
city = serializers.CharField()
def create(self, validated_data):
return Person(**validated_data)
person = Person("John", 30, "New York")
print(PersonSerializer(person).data)
This way you have a much better control over it. See docs.
Yes, the json module only knows how to serialize certain built-in types. An easy way to work with classes that have "fields" is the dataclasses module.
Example:
from dataclasses import dataclass, asdict
import json
#dataclass
class Person:
name: str
age: int
city: str
person = Person("John", 30, "New York")
print(json.dumps(asdict(person)))
The asdict() function converts the dataclass instance into a dict which json can serialize.
With Python 3.6+ you can use dataclasses in Python along with the asdict helper function to convert a dataclass instance to a Python dict object.
Note: For 3.6, you'll need a backport for the dataclasses module, as it only became a builtin to Python starting in 3.7.
import json
from dataclasses import dataclass, asdict
#dataclass
class Person:
name: str
age: int
city: str
person = Person("John", 30, "New York")
person_dict = asdict(person)
json_string = json.dumps(person_dict, indent=2)
print(json_string)
Out:
{
"name": "John",
"age": 30,
"city": "New York"
}
If you have a more complex use case, or end up with needing to (de)serialize a nested dataclass model, I'd check out an external library like the dataclass-wizard that supports such a use case in particular.
Related
Is there a way to convert json in to an object with only required fields, such that extra fields are ignored, and if the required fields do not exits throw an error?
If an object's field matches exactly with json fields, we could use something like this:
import json
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
test_json = '{"name": "user", "age":"50"}'
test_dict = json.loads(test_json)
test_obj = Person(**test_dict)
However, I would want the code to silently ignore extra fields in json for example:
test_json = '{"name": "user", "age":"50", "hobby":"swimming"}'
And if required fields are missing, throw an error
test_json = '{"name": "user", "hobby":"swimming"}'
I know you can add checks in when initializing the obj from the dictionary. But there are many fields and the json can come from different places thus formatting could change, so I wonder if there is a library could help achieve the above?
In order to get the extra fields in the object, you can use keyworded arguments (kwargs). For instance, this code can take any number of arguments (larger than 2 since the name and age must be there).
class Person:
def __init__(self, name, age, **kwargs):
self.name = name
self.age = age
print(kwargs)
You can tinker around with this and see if you can get it to fully work as desired.
this code allows you to get only dictionary keys that match the names of your class attributes using the inspect module:
import inspect
import json
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
getting the required field in your class initialization, this would recognize that you will need a name and age variables in your class __init__ method:
argspec = inspect.getfullargspec(Person.__init__)
required = argspec.args
if argspec.defaults:
required = required[:-len(argspec.defaults)]
required.remove('self')
keeping only the names that match object attributes:
test_json = '{"name": "user", "age":"50", "foo": "bar", "bar": "baz"}'
test_dict = json.loads(test_json)
test_dict = {k:v for k, v in test_dict.items() if k in required}
initializing the object:
test_obj = Person(**test_dict)
You can you Pydantic and define your class like in the example bellow:
import json
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
# Ignore the extra field
test_json_extra_field = '{"name": "user", "age":"50", "hobby":"swimming"}'
person_01 = Person(**json.loads(test_json_extra_field))
print(person_01)
# throw error because age is not in the json
test_json_no_required_field = '{"name": "user", "hobby":"swimming"}'
person_02 = Person(**json.loads(test_json_no_required_field))
print(person_02)
Pydantic BaseModel will ignore the extra field in test_json_extra_field and throw an error in test_json_no_required_field because age is not in the json info.
Often, when using a Python Package, I find myself using the str() function to convert a package's custom data-type to a string. If I were to try and create a Python Class for a module, how would I add compatibility to str() function for my package's class?
example:
class Person:
def __init__(self, name, age, likes, dislikes):
self.name = name
self.personality = {
"likes": likes,
"dislikes": dislikes
}
bill = Person("bill", 21, ["coding", "etc"], ["interviews", "socialising"])
strBill = str(bill) # This will store: '<__main__.Person object at 0x7fa68c2acac8>' but I want a dictionary containing all of the variables stored in this 'bill' class
print(strBill)
def __str__(self): will be used when you try to str(my_object). It would also be called in string interpolation such as f'This is my object: {my_object}'
def __repr__(self): will be used to represent your object in a console
>>> class A():
... def __str__(self):
... return 'im a string nicely formatted'
... def __repr__(self):
... return 'class A object'
...
>>> a = A()
>>> print(a)
im a string nicely formatted
>>> a
class A object
I am new to python and trying to map json response to python object in similar way like we do in Java using jackson library so that we can work with Java object.
sample json:-
{
"name":"John",
"age": 31,
"city": "New York"
}
Class to Map these parameters
class PersonDetails:
name: str
age: int
city: str
def __init__(self, name: str, age: int, city: str) -> None:
self.name = name
self.age = age
self.city = city
def get_age(self):
return self.age
def set_age(self, age):
self.age = age
def get_name(self):
return self.name
def set_name(self, name):
self.name = name
def get_city(self):
return self.city
def set_city(self, city):
self.city = city
and wanted to map json with this personDetails class.
Below code is written to parse json and i am getting values in jsonConverted variable but i want to use PersonDetails class so that i can fetch values with getters and setters similar way like we do in Java using jackson library.
responseData= json.dumps(response.json())
jsonConverted= json.loads(responseData, object_hook=lambda d: SimpleNamespace(**d))
print(jsonConverted.name, jsonConverted.age, jsonConverted.city)
is it possible to do that ?
I'm using dataclass and asdict from dataclasses, and I find that asdict doesn't work as I would expect when I introduce inheritance.
I use dataclasses to help me create dictionaries from classes so I can pass them into django.shortcuts.render.
from dataclasses import dataclass
from dataclasses import asdict
#dataclass
class Base:
name: str
class Test(Base):
def __init__(self, age, *args, **kwargs):
self.age = age
super(Test, self).__init__(*args, **kwargs)
test = Test(age=20, name="john doe")
print(asdict(test))
I would expect the output to be
{"age": 20, "name": "john doe"}
But what I get is only the keyword-value from the base-class
{"name": "john doe"}
The correct implementation for inheritance of a dataclass is covered in the docs:
#dataclass
class Base:
name: str
#dataclass
class Child(Base):
age: int
Without this, the __dataclass_fields__ attribute in the child class, which asdict uses to determine what should be in the dictionary, doesn't know about all of the fields you care about; it only has the inherited version:
>>> Test.__dataclass_fields__
{'name': Field(...)}
>>> Test.__dataclass_fields__ is Base.__dataclass_fields__
True
>>> Child.__dataclass_fields__
{'name': Field(...), 'age': Field(...)}
>>> Child.__dataclass_fields__ is Base.__dataclass_fields__
False
Also note you can simplify the imports to:
from dataclasses import asdict, dataclass
I'm trying to learn more about classes and OOP.
How can I have my Person class initialize with all the values of Entity but also with a value that may not be contained in the Entity class?
For example, both Person and Spirit inherit from Entity. However, only a Person would have a gender. How can I have Person initialize with gender as well?
After that, would I still be able to create an instance of Person and call describe() in the same way I've done below?
class Entity(object):
def __init__(self, state, name, age):
self.state = state
self.name = name
self.age = age
class Person(Entity):
def describe(self):
print "Identification: %s, %s, %s." % (self.state, self.name, self.age)
class Spirit(Entity):
pass # for now
steve = Person("human", "Steve", "23" # can I then list gender here?)
steve.describe()
Create a custom initializer on the sub-class and then call the parent class's initializer via super:
class Person(Entity):
def __init__(self, state, name, age, gender):
self.gender = gender
super(Person, self).__init__(state, name, age)
Transitionally, it looks like versions of Py 3.x (not sure which ones) allow this terse version of super():
def __init__(self, state, name, age, gender):
self.gender = gender
# Prototype initialization 3.x:
super().__init__(state, name, age)
Been experimenting with SQLAlchemy models using dataclasses, so when I zipped on by looking at all things Python inheritance, I felt this might extend the answer:
from dataclasses import dataclass
#dataclass
class Entity():
state: str
name: str
age: int
#dataclass
class Person(Entity):
gender: str
def describe(self):
print("State: {state}, Name: {name}, Age: {age}, Gender: {gender}"
.format(state=self.state, name=self.name, age=self.age, gender=self.gender))
man = Person("human", "humanname", 21, "cisgendered")
man.describe()