I've been practicing both using JSON files and OOP, so I figured I would do both at the same time, but I've been struggling with finding the most efficient way to create class objects based on JSON sections. after a lot of asking around, what I found worked best is using **
, even though a literally have no idea how it works. The code is:
import json
class Warship(object):
def __init__(self, name, gun_config, top_speed, belt_armor, displacement):
self.name = name
self.gun_config = gun_config
self.top_speed = top_speed
self.belt_armor =belt_armor
self.displacement = displacement
def __repr__(self):
return f'''{self.name}, {self.gun_config}, {self.top_speed} knts, {self.belt_armor} mm, {self.displacement} tons'''
workfile = json.load(open('warships.json', 'r'))
bisc = Warship(**workfile['kms'][0])
tirp = Warship(**workfile['kms'][1])
nc = Warship(**workfile['usn'][0])
wa = Warship(**workfile['usn'][1])
And here's the JSON file I made for this exercise:
{
"kms": [
{
"name": "Bismarck",
"gun_config": "3x2",
"top_speed": "29",
"belt_armor": "320",
"displacement": "41700"
},
{
"name": "Tirpitz",
"gun_config": "3x2",
"top_speed": "30",
"belt_armor": "320",
"displacement": "41700"
}
],
"usn": [
{
"name": "North Carolina",
"gun_config": "3x3",
"top_speed": "28",
"belt_armor": "305",
"displacement": "36600"
},
{
"name": "Washington",
"gun_config": "3x3",
"top_speed": "29",
"belt_armor": "305",
"displacement": "36600"
}
]
}
Is using the **
like this the best way to create Python class objects based on JSON objects or are there better ways?
CodePudding user response:
Using the **
operator is probably the preferred way to do it, as long as your constructor parameters match the names of your JSON attributes exactly (like they do in your example) and as long as there's not additional parameters on the constructor that could wreak havoc:
def Message:
def __init__(greeting: str, wipe_c_drive: bool = False):
self.greeting = greeting
if wipe_c_drive:
shutil.rmtree('C:/')
workfile = json.load(open('greetings.json', 'r'))
hello = Message(**workfile['hello'])
And the data in greetings.json
:
{
"hello": {
"greeting": "Hello there!"
}
}
What could possibly go wrong, right?
The **
operator is a 'spreading' operator, it take a key/value pair data object like a dictionary, and spreads it - typically to provide keyword arguments for a function:
def say(greeting, name):
print(f'{greeting} to you, {name}!')
d = {'greeting': 'hello', 'name': 'AnFa')
say(**d)
This works in your case as well, since a JSON object is loaded as a dictionary and thus can be spread using the **
operator. And since the attributes of your JSON object exactly match the names of your keyword parameters for the constructor, it works.
Similarly, the *
operators spreads a list:
xs = ['hello', 'AnFa']
say(*xs)
This has the same result, but tries to pass the values as positional parameters.
Knowing the above, you may also understand why you sometimes see this:
def some_func(x, *args, **kwargs):
print(x)
some_other_func(*args, **kwargs)
Here, the *args
and **kwargs
work to capture positional and keyword parameters in args
(arguments) and kwargs
(keyword arguments) and the second time, they spread them back into the call to some_other_func
. Those names args
and kwargs
are not magical, they're just conventions.
If you want something a bit less risky (because of the reasons explained above), you could give your class 'to_json' and 'from_json' methods, but fairly soon things will get complicated and you may want to look into existing libraries instead, as user @LeiYang suggests (and there are many of those).