tl;dr: It should be a
@dataclass.
code which sets values in a location class from values from JSON data.
The vocabulary word you're looking for is "object attribute".
The code sets attributes of a location object.
Using such terminology will prove helpful when you're googling.
BTW, one can set a class attribute, offering
global variable or
singleton
behavior, scoped to consumers of the class.
But that's not something the OP code does.
naming
def create_location_class_from_json(json_data):
No, the class Location:
statement is what creates a Location class.
This function is creating a location object,
an instance of that class.
Simply name it create_location_from_json
, please.
location_obj = Location()
Just call it a location
, please.
Suppose you opened a novel and started reading
this totally normal dialog:
"Hello, Alice person," said Bob.
"I am Bob person, pleased to meet you."
It's clear from context that there's some people in
this situation; we don't need to call it out explicitly.
So, too, with program identifiers.
optional entries
This code is smart enough to anticipate that an entry
might be missing, and deal with it.
if name_str in json_data:
location_obj.name = json_data["name"]
That json_data["name"]
dereference would blow up with KeyError
if the name was missing.
But dict
offers a more convenient way to cope with
this common concern:
location_obj.name = json_data.get("name")
This is a little different from the OP code's behavior,
as it will assign .name = None
in the missing case,
but that's actually desirable -- see the ToC section.
hardcode
I feel your foo_str
constants are perhaps a little
over the top, given the strictly function local context,
but let's go with it.
You apparently meant to type "name"
exactly
once.
So that would give us
location_obj.name = json_data.get(name_str)
table of contents
It's polite for a python class to tell the Gentle Reader
about all its possible instance attributes, in one place.
It serves as a heads up, here's what to expect as you're
reading the rest of the code.
So we might have a def __init__(self):
constructor
which assigns self.name = None
, plus several other attributes.
But here, there's essentially no behavior to the class,
it's just a bunch of storage locations.
That is the perfect fit for a dataclass,
so let's use that decorator, annotating types as we go.
from dataclasses import dataclass
@dataclass
class Location:
name: str
street: str
city: str
state: str
postal_code: str
lat_long: tuple[float, float]
remote: bool
I renamed self.lat_long
to match
convention,
and also changed its type to something more
useful than strings.
(Note that ZIP must remain str
,
due to e.g. Boston zipcodes like 02108.)
You could break out a separate class Address:
if you
want to mirror the nested JSON structure.
I'm going with what you wrote, which is perfectly
ordinary and fine.
Also, I understand why you called it json_data
,
and that's not such a terrible name, you could keep it.
But simply data
would be better, since it contains
the result of a JSON parse.
Or perhaps location_data
.
creating an instance object
location_obj = create_location_class_from_json(json_data)
I started out thinking that this should
be as simple as
location_obj = Location(**json_data)
However there are a few details to attend to:
- extra fields, such as "countryCode"
- combining the lat + long fields
- renaming fields like "street1" -> "street"
- renaming fields from camelCase to snake_case
- nesting of fields like "address"
None of these are hard, merely tedious.
It's a matter of modeling how your desired result
differs from the input data that arrived in JSON form.
Here is complete code for one take on that.
from dataclasses import dataclass
from pprint import pp
@dataclass
class Location:
name: str
street: str
city: str
state: str
postal_code: str
lat_long: tuple[float, float]
remote: bool
if __name__ == "__main__":
json_data = {
"name": "Park",
"address": {
"street": "Street",
"city": "City",
"state": "ST",
"countryCode": "US",
"postal_code": "12345",
},
"latitude": "0",
"longitude": "0",
"remote": False,
}
d = json_data.copy()
d.update(**d.pop("address"))
del d["countryCode"]
d["lat_long"] = (float(d.pop("latitude")), float(d.pop("longitude")))
location_obj = Location(**d)
print(location_obj.name, "\n")
print(location_obj, "\n")
pp(location_obj)
output:
Park
Location(name='Park', street='Street', city='City', state='ST', postal_code='12345', lat_long=(0.0, 0.0), remote=False)
Location(name='Park',
street='Street',
city='City',
state='ST',
postal_code='12345',
lat_long=(0.0, 0.0),
remote=False)
camelCase translation
Doing "$ pip install
dataclass-wizard"
can help with that need to cheat on the "postalCode" rename.
However it bumps up against the other mismatches
mentioned above, so I'll omit an example here.
optional entries, revisited
Suppose the incoming JSON data sometimes
omits any mention of remote status.
The class definition could cope with that in this way.
...
remote: bool | None = None
Now if we del d["remote"]
we see output ending with
...
lat_long=(0.0, 0.0),
remote=None)
This still differs from the OP code's behavior,
which wouldn't define a .remote
attribute at all.
As mentioned above, that's probably not a great
design decision. Most consumers of the Location
class will expect that all attributes mentioned
in the constructor's ToC are available for dereferencing.
I encourage you to design classes with that in mind.
main guard
Thank you for including this.
if __name__ == "__main__":
It allows other modules to safely import
this
one without side effects.
Please keep up that good habit.