<jupyter_start><jupyter_text>Working With JSON Data in Python    Table of Contents * [A (Very) Brief History of JSON](a_(very)_brief_history_of_json)* [Look, it’s JSON!](look,_it’s_json!)* [Python Supports JSON Natively!](python_supports_json_natively!)* [A Little Vocabulary](a_little_vocabulary)* [Serializing JSON](serializing_json)* [A Simple Serialization Example](a_simple_serialization_example)* [Some Useful Keyword Arguments](some_useful_keyword_arguments)* [Deserializing JSON](deserializing_json)    * [A Simple Deserialization Example](a_simple_deserialization_example)* [Encoding and Decoding Custom Python Objects](encoding_and_decoding_custom_python_objects)    * [Simplifying Data Structures](simplifying_data_structures)        * [Encoding Custom Types](encoding_custom_types)        * [Decoding Custom Types](decoding_custom_types)--- Since its inception, [JSON](https://en.wikipedia.org/wiki/JSON) has quickly become the de facto standard for information exchange. Chances are you’re here because you need to transport some data from here to there. Perhaps you’re gathering information through an [API](https://realpython.com/api-integration-in-python/) or storing your data in a [document database](https://realpython.com/introduction-to-mongodb-and-python/). One way or another, you’re up to your neck in JSON, and you’ve got to Python your way out. Luckily, this is a pretty common task, and—as with most common tasks—Python makes it almost disgustingly easy. Have no fear, fellow Pythoneers and Pythonistas. This one’s gonna be a breeze! **So, we use JSON to store and exchange data?** Yup, you got it! It’s nothing more than a standardized format the community uses to pass data around. Keep in mind, JSON isn’t the only format available for this kind of work, but [XML](https://en.wikipedia.org/wiki/XML) and [YAML](http://yaml.org/) are probably the only other ones worth mentioning in the same breath.  A (Very) Brief History of JSON Not so surprisingly, **J**ava**S**cript **O**bject **N**otation was inspired by a subset of the JavaScript programming language dealing with object literal syntax. They’ve got a [nifty website](https://www.json.org/) that explains the whole thing. Don’t worry though: JSON has long since become language agnostic and exists as its own standard, so we can thankfully avoid JavaScript for the sake of this discussion.Ultimately, the community at large adopted JSON because it’s easy for both humans and machines to create and understand.  Look, it’s JSON! Get ready. I’m about to show you some real life JSON—just like you’d see out there in the wild. It’s okay: JSON is supposed to be readable by anyone who’s used a C-style language, and Python is a C-style language…so that’s you!```json{    "firstName": "Jane",    "lastName": "Doe",    "hobbies": ["running", "sky diving", "singing"],    "age": 35,    "children": [        {            "firstName": "Alice",            "age": 6        },        {            "firstName": "Bob",            "age": 8        }    ]}``` **Wait, that looks like a Python dictionary!** I know, right? It’s pretty much universal object notation at this point, but I don’t think UON rolls off the tongue quite as nicely. Feel free to discuss alternatives in the comments.  Python Supports JSON Natively! Python comes with a built-in package called [json](https://docs.python.org/3/library/json.html) for encoding and decoding JSON data.Just throw this little guy up at the top of your file:```pythonimport json```  A Little VocabularyThe process of encoding JSON is usually called **serialization**. This term refers to the transformation of data into a series of bytes (hence serial) to be stored or transmitted across a network. You may also hear the term **marshaling**, but that’s a whole other discussion. Naturally, **deserialization** is the reciprocal process of decoding data that has been stored or delivered in the JSON standard. **Yikes! That sounds pretty technical**. Definitely. But in reality, all we’re talking about here is _reading_ and _writing_. Think of it like this: _encoding_ is for _writing_ data to disk, while _decoding_ is for _reading_ data into memory.  Serializing JSON What happens after a computer processes lots of information? It needs to take a data dump. Accordingly, the `json` library exposes the `dump()` method for writing data to files. There is also a `dumps()` method (pronounced as “dump-s”) for writing to a Python string. Simple Python objects are translated to JSON according to a fairly intuitive conversion. |`Python`|`JSON`||:--|:--||`dict`|`object`||`list tuple`|`array`||`str`|`string`||`int long float `|`number`||`True`|`true`||`False`|`false`||`None`|`null`|  A Simple Serialization ExampleImagine you’re working with a Python object in memory that looks a little something like this:<jupyter_code>class Student:
    pass

data = {
    "students": [
        {
            "Name": "<NAME>",
            "Grade": (14.0, 20),
        },
        {
            "Name": "<NAME>",
            "Grade": None,
            "is_in_class": True
        }
    ]
}
# encode, serialization
output = json.dumps(data)
output
# decode, deserialization
json.loads(output)<jupyter_output><empty_output><jupyter_text>It is critical that you save this information to disk, so your mission is to write it to a file. Using Python’s context manager, you can create a file called `data_file.json` and open it in write mode. (JSON files conveniently end in a `.json` extension.)<jupyter_code>import json

with open("data_file.json", "w") as f:
    json.dump(data, f)
with open("data_file.json") as f:
    data = json.load(f)
data<jupyter_output><empty_output><jupyter_text>Note that `dump()` takes two positional arguments:1. the data object to be serialized2. the file-like object to which the bytes will be written. Or, if you were so inclined as to continue using this serialized JSON data in your program, you could write it to a native Python `str` object.<jupyter_code>json_string = json.dumps(data)
json_string<jupyter_output><empty_output><jupyter_text>Notice that the file-like object is absent since you aren’t actually writing to disk. Other than that, `dumps()` is just like `dump()`.  Some Useful Keyword Arguments Remember, JSON is meant to be easily readable by humans, but readable syntax isn’t enough if it’s all squished together. Plus you’ve probably got a different programming style than me, and it might be easier for you to read code when it’s formatted to your liking. > **NOTE:** Both the `dump()` and `dumps()` methods use the same keyword arguments. The first option most people want to change is whitespace. You can use the `indent` keyword argument to specify the indentation size for nested structures. Check out the difference for yourself by using `data`, which we defined above, and running the following commands in a console:<jupyter_code>print(json.dumps(data))
print(json.dumps(data, indent=2))
with open("json_data.json", 'w') as f:
    json.dump(data, f, indent=2)<jupyter_output><empty_output><jupyter_text>Another formatting option is the separators keyword argument. By default, this is a 2-tuple of the separator strings `(", ", ": ")`, but a common alternative for compact JSON is `(",", ":")`. Take a look at the sample JSON again to see where these separators come into play. There are others, like `sort_keys`, but I have no idea what that one does. You can find a whole list in the docs if you’re curious.<jupyter_code>print(json.dumps(data, indent=4, separators=(",", ": ")))<jupyter_output>{
    "students": [
        {
            "Name": "<NAME>",
            "Grade": [
                14.0,
                20
            ]
        },
        {
            "Name": "<NAME>",
            "Grade": null,
            "is_in_class": true
        }
    ]
}<jupyter_text>Deserializing JSONGreat, looks like you’ve captured yourself some wild JSON! Now it’s time to whip it into shape. In the json library, you’ll find `load()` and `loads()` for turning JSON encoded data into Python objects. Just like serialization, there is a simple conversion table for deserialization, though you can probably guess what it looks like already. |`JSON`|`Python`||:--|:--||`object`|`dict`||`array`|`list`||`string`|`str`||`number (int)`|`int`||`number (real)`|`float`||`true`|`True`||`false`|`False`||`null`|`None`| Technically, this conversion isn’t a perfect inverse to the serialization table. That basically means that if you encode an object now and then decode it again later, you may not get exactly the same object back. I imagine it’s a bit like teleportation: break my molecules down over here and put them back together over there. Am I still the same person? In reality, it’s probably more like getting one friend to translate something into Japanese and another friend to translate it back into English. Regardless, the simplest example would be encoding a `tuple` and getting back a `list` after decoding, like so:<jupyter_code>blackjack_hand = (8, "Q")
encoded_hand = json.dumps(blackjack_hand)
decoded_hand = json.loads(encoded_hand)
blackjack_hand == decoded_hand
type(blackjack_hand)
type(decoded_hand)
blackjack_hand == tuple(decoded_hand)<jupyter_output><empty_output><jupyter_text>A Simple Deserialization Example This time, imagine you’ve got some data stored on disk that you’d like to manipulate in memory. You’ll still use the context manager, but this time you’ll open up the existing `data_file.json` in read mode<jupyter_code>with open("data_file.json", "r") as read_file:
    data = json.load(read_file)<jupyter_output><empty_output><jupyter_text>Things are pretty straightforward here, but keep in mind that the result of this method could return any of the allowed data types from the conversion table. This is only important if you’re loading in data you haven’t seen before. In most cases, the root object will be a `dict` or a `list`. If you’ve pulled JSON data in from another program or have otherwise obtained a string of JSON formatted data in Python, you can easily deserialize that with `loads()`, which naturally loads from a string:<jupyter_code>json_string = """
{
    "researcher": {
        "name": "<NAME>",
        "species": "Betelgeusian",
        "relatives": [
            {
                "name": "<NAME>",
                "species": "Betelgeusian"
            }
        ]
    }
}
"""
data = json.loads(json_string)<jupyter_output><empty_output><jupyter_text>Encoding and Decoding Custom Python ObjectsWhat happens when we try to serialize a custom object you’re working on?<jupyter_code>class CustomType:
    pass
data = {
    'custom_type': CustomType()
}
json.dumps(data)<jupyter_output><empty_output><jupyter_text>Although the `json` module can handle most built-in Python types, it doesn’t understand how to encode customized data types by default. It’s like trying to fit a square peg in a round hole—you need a buzzsaw and parental supervision.  Simplifying Data Structures Now, the question is how to deal with more complex data structures. Well, you could try to encode and decode the JSON by hand, but there’s a slightly more clever solution that’ll save you some work. Instead of going straight from the custom data type to JSON, you can throw in an intermediary step. All you need to do is represent your data in terms of the built-in types `json` already understands. Essentially, you translate the more complex object into a simpler representation, which the json module then translates into JSON. It’s like the transitive property in mathematics: if `A = B` and `B = C`, then `A = C`. To get the hang of this, you’ll need a complex object to play with. You could use any custom class you like, but Python has a built-in type called `complex` for representing complex numbers, and it isn’t serializable by default. So, for the sake of these examples, your complex object is going to be a `complex` object. Confused yet?<jupyter_code>z = 3 + 8j
type(z)
json.dumps(z)<jupyter_output><empty_output><jupyter_text>A good question to ask yourself when working with custom types is **What is the minimum amount of information necessary to recreate this object?** In the case of complex numbers, you only need to know the real and imaginary parts, both of which you can access as attributes on the `complex` object:<jupyter_code>z.real, z.imag<jupyter_output><empty_output><jupyter_text>Passing the same numbers into a `complex` constructor is enough to satisfy the `__eq__` comparison operator:<jupyter_code>complex(3, 8) == z<jupyter_output><empty_output><jupyter_text>Breaking custom data types down into their essential components is critical to both the serialization and deserialization processes.  Encoding Custom TypesTo translate a custom object into JSON, all you need to do is provide an encoding function to the `dump()` method’s `default` parameter. The `json` module will call this function on any objects that aren’t natively serializable. Here’s a simple decoding function you can use for practice:<jupyter_code>def encode_complex(z):
    if isinstance(z, complex):
        return (z.real, z.imag)
    else:
        type_name = z.__class__.__name__
        raise TypeError(f"Object of type '{type_name}' is not JSON serializable")<jupyter_output><empty_output><jupyter_text>Notice that you’re expected to raise a `TypeError` if you don’t get the kind of object you were expecting. This way, you avoid accidentally serializing any other object type. Now you can try encoding complex objects for yourself!<jupyter_code>json.dumps(3 + 4j, default=encode_complex)
json.dumps(CustomType(), default=encode_complex)<jupyter_output><empty_output><jupyter_text>> **Why did we encode the complex number as a `tuple`?** Great question! That certainly wasn’t the only choice, nor is it necessarily the best choice. In fact, this wouldn’t be a very good representation if you ever wanted to decode the object later, as you’ll see shortly. The other common approach is to subclass the standard `JSONEncoder` and override its `default()` method:<jupyter_code>class ComplexEncoder(json.JSONEncoder):
    def default(self, z):
        if isinstance(z, complex):
            return (z.real, z.imag)
        else:
            return super().default(z)<jupyter_output><empty_output><jupyter_text>Instead of raising the `TypeError` yourself, you can simply let the base class handle it. You can use this either directly in the `dump()` method via the cls parameter or by creating an instance of the encoder and calling its `encode()` method:<jupyter_code>json.dumps(2 + 5j, cls=ComplexEncoder)
encoder = ComplexEncoder()
encoder.encode(3+6j)
json.dumps(CustomType(), cls=ComplexEncoder)<jupyter_output><empty_output><jupyter_text>Decoding Custom Types While the real and imaginary parts of a complex number are absolutely necessary, they are actually not quite sufficient to recreate the object. This is what happens when you try encoding a complex number with the `ComplexEncoder` and then decoding the result:<jupyter_code>complex_json = json.dumps(4 + 17j, cls=ComplexEncoder)
complex_json
json.loads(complex_json)<jupyter_output><empty_output><jupyter_text>All you get back is a list, and you’d have to pass the values into a `complex` constructor if you wanted that complex object again. Recall our discussion about teleportation. What’s missing is metadata, or information about the type of data you’re encoding. I suppose the question you really ought ask yourself is What is the minimum amount of information that is both **necessary** and **sufficient** to recreate this object? The `json` module expects all custom types to be expressed as `objects` in the JSON standard. For variety, you can create a JSON file this time called `complex_data.json` and add the following `object` representing a complex number:<jupyter_code>%%writefile complex_data.json
{
    "__complex__": true,
    "real": 42,
    "imag": 36
}<jupyter_output>Overwriting complex_data.json<jupyter_text>See the clever bit? That `"__complex__"` key is the metadata we just talked about. It doesn’t really matter what the associated value is. To get this little hack to work, all you need to do is verify that the key exists:<jupyter_code>def decode_complex(dct):
    if "__complex__" in dct:
        return complex(dct["real"], dct["imag"])

    return dct<jupyter_output><empty_output><jupyter_text>If `"__complex__"` isn’t in the dictionary, you can just return the object and let the default decoder deal with it. Every time the `load()` method attempts to parse an `object`, you are given the opportunity to intercede before the default decoder has its way with the data. You can do this by passing your decoding function to the `object_hook` parameter. Now play the same kind of game as before:<jupyter_code>with open("complex_data.json") as complex_data:
    data = complex_data.read()
    z = json.loads(data, object_hook=decode_complex)
z<jupyter_output><empty_output><jupyter_text>While `object_hook` might feel like the counterpart to the `dump()` method’s `default` parameter, the analogy really begins and ends there. This doesn’t just work with one object either. Try putting this list of complex numbers into `complex_data.json` and running the script again:<jupyter_code>%%writefile complex_data.json

[
  {
    "__complex__":true,
    "real":42,
    "imag":36
  },
  {
    "__complex__":true,
    "real":64,
    "imag":11
  }
]<jupyter_output>Overwriting complex_data.json<jupyter_text>If all goes well, you’ll get a list of complex objects:<jupyter_code>with open("complex_data.json") as complex_data:
    data = complex_data.read()
    numbers = json.loads(data, object_hook=decode_complex)
numbers<jupyter_output><empty_output><jupyter_text>You could also try subclassing `JSONDecoder` and overriding `object_hook`, but it’s better to stick with the lightweight solution whenever possible.<jupyter_code>class ComplexDecoder(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)

    def object_hook(self, dct):
        if "__complex__" in dct:
            return complex(dct["real"], dct["imag"])
        return dct
with open("complex_data.json") as complex_data:
    data = complex_data.read()
    numbers = json.loads(data, cls=ComplexDecoder)
numbers<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08928668589150661, 'fraction_numerical': 0.002557266445399641, 'mean_word_length': 4.664098613251156, 'pattern_counts': {'":': 39, '<': 96, '<?xml version=': 0, '>': 98, 'https://': 6, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '557930', 'license': 'MIT', 'n_tokens_mistral': 5173, 'n_tokens_neox': 5024, 'n_words': 2243, 'path': 'Python/05. Modules/01.1 json.ipynb', 'repo_name': 'saramazaheri/CS-Tutorial', 'size': '18426'}