3
\$\begingroup\$

I am retrieving data from Postgres (jsonb type) and I need to return an OrderedDict that has a predictable order for human and machine consumption. There are some common(ish) keys that should be used to direct precedence of values of common types (based on a predefined order and if sort_order is defined). Otherwise, sort order should fall back to key based lexicographic ordering.

The general intent is to have a predictable, 'sane', represenation of composite dicts.

The basic algorithm is:

  1. Dicts come before lists
  2. Values that are NOT iterables or mapping take precedence over objects that are.
  3. Values of the same type whose keys are not in sort_order are considered equal and should be sorted lexicographically.
  4. Obj A takes precedence over Obj B if type(A[0]) == type(B) and [0] in sort_order not B[0] in sort_order
  5. if all([type(A[1]) == type(B[1]), A[0] in sort_order, B[0] in sort_order]) then the index position of the object key is the precedence determinant.

I have attempted several implementations, but I have not been able to come up with anything that I would consider pythonic/elegant.

Here is the latest incarnation:

# -*- coding: utf-8 -*-

import json
from collections import OrderedDict


def dict_sort(obj, sort_order=None):
    def seq(s, o=None, v=None):
        return str(s) + str(o) + str(v) if o is not None else str(s)

    order_seq = None
    if sort_order is not None and obj[0] in sort_order:
        order_seq = [i for i, v in enumerate(sort_order) if v == obj[0]][0]

    if isinstance(obj[1], dict):
        return seq(2, order_seq, obj[0]) if order_seq else seq(3)
    elif isinstance(obj[1], list):
        return seq(4, order_seq, obj[0]) if order_seq else seq(5)
    else:
        return seq(0, order_seq, obj[0]) if order_seq else seq(1)


def comp_sort(obj, sort_order=None):
    data = OrderedDict()
    if isinstance(obj, dict):
        for key, value in sorted(obj.items(), key=lambda d: dict_sort(d, sort_order)):
            if isinstance(value, dict) or isinstance(value, list):
                data[key] = comp_sort(value, sort_order)
            else:
                data[key] = value
    elif isinstance(obj, list):
        try:
            return sorted(obj)
        except:
            items = []
            for value in obj:
                if isinstance(value, dict) or isinstance(value, list):
                    items.append(comp_sort(value, sort_order))
                else:
                    items.append(value)
            return items
    return data

# thx herk

Here is a sample data set.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You can use a list comprehension to avoid verbosity:

        items = []
        for value in obj:
            if isinstance(value, dict) or isinstance(value, list):
                items.append(comp_sort(value, sort_order))
            else:
                items.append(value)
        return items

Becomes:

is_dict_or_list = isinstance(value, dict) or isinstance(value, list) 
return [comp_sort(value, sort_order) if is_dict_or_list else value
               for value in obj ]

Remember to specify:

except ExpectedError:

And not

except:

The latter will swallow anything and harden debugging.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.