Home > front end >  json.dumps() sort keys except top level
json.dumps() sort keys except top level

Time:02-02

Need to dump a dict of objects. I'd like to sort the keys of the nested objects, while preserving the order of top level keys. The following sorts all keys:

json.dumps(dict_of_objs, sort_keys=True, default=vars)    

Thanks


Bad Solution

I could run something like the following but this seems like a lot of unnecessary processing:

for key in dict_of_objs.keys():
    sorted_str = json.dumps(dict_of_objs[key], sort_keys=True, default=vars)
    dict_of_objs[key] = json.loads(sorted_str)

json.dumps(dict_of_objs)    

CodePudding user response:

My solution I think it the same as yours but I think you can just make lt return False.

from json import JSONEncoder


class WrappedDict(dict):

    def items(self):
        # https://github.com/python/cpython/blob/main/Lib/json/encoder.py#L353
        # Return our wrapped items to sorted.
        return (WrappedItem(item) for item in super().items())


class WrappedItem(tuple):

    def __lt__(self, other):
        # https://docs.python.org/3/library/functions.html#sorted
        # Fake out sorted by just saying no item, ie. (k, v), is ever less than.
        return False

root = {
    "b": 2,
    "a": 1,
    "c": {
        "b": 2,
        "a": [{
            "z": 15,
            "a": 1,
        }]
    }
}

wrapped_root = WrappedDict(root)


print ('root')
print (root)

print ('sort_keys=True with wrapped root')
print (JSONEncoder(sort_keys=True).encode(wrapped_root))

print ('sort_keys=True with root')
print (JSONEncoder(sort_keys=True).encode(root))

print ('sort_keys=False with wrapped_root')
print (JSONEncoder(sort_keys=False).encode(wrapped_root))

print ('sort_keys=False with root')
print (JSONEncoder(sort_keys=False).encode(root))

Output

root
{'b': 2, 'a': 1, 'c': {'b': 2, 'a': [{'z': 15, 'a': 1}]}}
sort_keys=True with wrapped root
{"b": 2, "a": 1, "c": {"a": [{"a": 1, "z": 15}], "b": 2}}
sort_keys=True with root
{"a": 1, "b": 2, "c": {"a": [{"a": 1, "z": 15}], "b": 2}}
sort_keys=False with wrapped_root
{"b": 2, "a": 1, "c": {"b": 2, "a": [{"z": 15, "a": 1}]}}
sort_keys=False with root
{"b": 2, "a": 1, "c": {"b": 2, "a": [{"z": 15, "a": 1}]}}

CodePudding user response:

Edit

Comments pointed out that there's no need to know the values of keys, we can just disable sorting altogether by having lt always return False:

#!/usr/bin/env python3
import json
class UnSortableString(str):
    def __lt__(self, other):
        return False

normal_dict = {"zzz" : [5,4,3], "aaa": [100,3] }
print(json.dumps(normal_dict, sort_keys=True))

mydict = {UnSortableString("zzz") : [5,4,3], UnSortableString("aaa"): [100,3] }
print(json.dumps(mydict, sort_keys=True))

Outputs:

{"aaa": [100, 3], "zzz": [5, 4, 3]}
{"zzz": [5, 4, 3], "aaa": [100, 3]}

Since in my case I know the values of the top level keys, I came up with the following hacky solution:

#!/usr/bin/env python3
import json
class FakeString(str):
    order = ["first", "second", "last"]
    def __lt__(self, other):
        if self in FakeString.order and other in FakeString.order:
            return FakeString.order.index(self) < FakeString.order.index(other)
        return super.__lt__(self, other) 

normal_dict = {"last" : [5,4,3], "second": [100,3] }
print(json.dumps(normal_dict, sort_keys=True))

mydict = {FakeString("last") : [5,4,3], FakeString("second"): [100,3] }
print(json.dumps(mydict, sort_keys=True))

Outputs:

{"last": [5, 4, 3], "second": [100, 3]}
{"second": [100, 3], "last": [5, 4, 3]}
  •  Tags:  
  • Related