<jupyter_start><jupyter_text>json package — JavaScript Object NotationUsing the `json package` to code the JSON Representation of Rankine Cycle with Python * [The JSON Representation of Rankine Cycle](./PyThermo-RankineCycle-JSON-UML.ipynb)`json package` https://pymotw.com/3/json/index.html**Purpose:**	Encode Python objects as JSON strings, and decode JSON strings into Python objects.The json module provides an API for converting in-memory Python objects to a serialized representation known as **JavaScript Object Notation (JSON).** JSON has the benefit of having implementations in many languages (especially JavaScript). It is most widely used for communicating between the web server and client in a REST API, but is also useful for other inter-application communication needs.  Encoding and Decoding Simple Data TypesThe **encoder** understands Python’s native types by default (`str, int, float, list, tuple, and dict`).<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

data_string = json.dumps(data)
print('JSON:', data_string)<jupyter_output><empty_output><jupyter_text>**Values** are encoded in a manner superficially similar to Python’s `repr()` output. **Encoding**, then re-decoding may **not give exactly the same** type of object.In particular, **tuples** become **lists**.<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA   :', data)

data_string = json.dumps(data)
print('ENCODED:', data_string)

decoded = json.loads(data_string)
print('DECODED:', decoded)

print('ORIGINAL:', type(data[0]['b']))
print('DECODED :', type(decoded[0]['b']))<jupyter_output><empty_output><jupyter_text>Human-consumable vs. Compact OutputAnother benefit of `JSON` is that the results are human-readable. The `dumps()` function accepts several arguments to make the output even nicer. For example, the **sort_keys** flag tells the encoder to output the keys of a dictionary in sorted, instead of random, order.<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

unsorted = json.dumps(data)
print('JSON:', json.dumps(data))
print('SORT:', json.dumps(data, sort_keys=True))

first = json.dumps(data, sort_keys=True)
second = json.dumps(data, sort_keys=True)

print('UNSORTED MATCH:', unsorted == first)
print('SORTED MATCH  :', first == second)<jupyter_output><empty_output><jupyter_text>For highly-nested data structures, specify a value for **indent** so the output is formatted nicely as well.When **indent** is a non-negative integer, the output with leading spaces for each level of the data structure matching the indent level.<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('NORMAL:', json.dumps(data, sort_keys=True))
print('INDENT:', json.dumps(data, sort_keys=True, indent=2))<jupyter_output><empty_output><jupyter_text>**Verbose** output like this **increases the number of bytes** needed to transmit the same amount of data, however, so it is not intended for use in a production environment. In fact, it is possible to **adjust the settings for separating data** in the encoded output to make it even more **compact** than the default.The **separators** argument to `dumps()` should be a `tuple` containing the strings to separate items in a list and keys from values in a dictionary. The default is **(', ', ': ')**.By **removing the whitespace**, a more compact output is produced.<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))

print('repr(data)             :', len(repr(data)))

plain_dump = json.dumps(data)
print('\ndumps(data)            :', len(plain_dump))
print(plain_dump)

small_indent = json.dumps(data, indent=2)
print('\ndumps(data, indent=2)  :', len(small_indent))
print(small_indent)

with_separators = json.dumps(data, separators=(',', ':'))
print('\ndumps(data, separators):', len(with_separators))
print(with_separators)<jupyter_output><empty_output><jupyter_text>Encoding DictionariesThe JSON format expects the **keys** to a dictionary to be **strings**. Trying to encode a dictionary with **non-string types** as keys produces a **TypeError**.One way to work around that limitation is to tell the encoder to **skip over non-string keys** using the **skipkeys** argument:Rather than raising an exception, the **non-string key is ignored**.<jupyter_code>import json

data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]

print('First attempt')
try:
    print(json.dumps(data))
except TypeError as err:
    print('ERROR:', err)

print()
print('Second attempt')
print(json.dumps(data, skipkeys=True))<jupyter_output><empty_output><jupyter_text>Working with Custom TypesAll of the examples so far have used Pythons **built-in types** because those are supported by **json natively.** It is common to need to encode **custom** classes, as well, and there are two ways to do that. Given this class to encode:<jupyter_code>class MyObj:

    def __init__(self, s):
        self.s = s

    def __repr__(self):
        return '<MyObj({})>'.format(self.s)<jupyter_output><empty_output><jupyter_text>The simple way of encoding a **MyObj** instance is to define **a function** to convert an unknown type to a **known** type. It does not need to do the encoding, so it should just convert one object to another.In **convert_to_builtin_type()**, instances of classes not recognized by json are converted to dictionaries with enough information to re-create the object if a program has access to the Python modules necessary.<jupyter_code>import json

obj = MyObj('instance value goes here')

print('First attempt')
try:
    print(json.dumps(obj))
except TypeError as err:
    print('ERROR:', err)


def convert_to_builtin_type(obj):
    print('default(', repr(obj), ')')
    
    # Convert objects to a dictionary of their representation
    d = {
        '__class__': obj.__class__.__name__,
        '__module__': obj.__module__,
    }
    d.update(obj.__dict__)
    
    return d


print('\nWith default')
print(json.dumps(obj, default=convert_to_builtin_type))<jupyter_output><empty_output><jupyter_text>To **decode** the results and create a `MyObj()` instance, use the `object_hook` argument to `loads()` to tie in to the decoder so the class can be imported from the module and used to create the instance.The `object_hook` is called for each dictionary decoded from the incoming data stream, providing a chance to convert the dictionary to another type of object. The hook function should return the object the calling application should receive instead of the dictionarySince `json` converts string values to unicode objects, they need to be re-encoded as ASCII strings before they can be used as keyword arguments to the class constructor.<jupyter_code>import json


def dict_to_object(d):
    if '__class__' in d:
        class_name = d.pop('__class__')
        module_name = d.pop('__module__')
        module = __import__(module_name)
        print('MODULE:', module.__name__)
        
        class_ = getattr(module, class_name)
        print('\nCLASS:', class_)
        args = {
            key: value
            for key, value in d.items()
        }
        print('\nINSTANCE ARGS:', args)
        inst = class_(**args)
    else:
        inst = d
    return inst


encoded_object = '''
    [{"s": "instance value goes here",
      "__module__": "__main__", "__class__": "MyObj"}]
    '''

myobj_instance = json.loads(
    encoded_object,
    object_hook=dict_to_object,
)
print(myobj_instance)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1267272009474931, 'fraction_numerical': 0.0039478878799842085, 'mean_word_length': 4.905205905205905, 'pattern_counts': {'":': 3, '<': 38, '<?xml version=': 0, '>': 38, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, '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': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '107545', 'license': 'MIT', 'n_tokens_mistral': 2330, 'n_tokens_neox': 2275, 'n_words': 834, 'path': 'notebook/Unit4-3-PyThermo-Python-JSON.ipynb', 'repo_name': 'hervey-su/home', 'size': '7599'}