'Asserts that 2 complex data structures are json equivalent.
We use data structures which serialize down to json throughout
the code, and often times we just need to know that these are
json equivalent. This means that list order is not important,
and should be sorted.
Because this is a recursive set of assertions, when failure
happens we want to expose both the local failure and the
global view of the 2 data structures being compared. So a
MismatchError which includes the inner failure as the
mismatch, and the passed in expected / observed as matchee /
matcher.'

def assertJsonEqual(self, expected, observed, message=''):

    if isinstance(expected, six.string_types):
        expected = jsonutils.loads(expected)
    if isinstance(observed, six.string_types):
        observed = jsonutils.loads(observed)
    def sort_key(x):
        if (isinstance(x, (set, list)) or isinstance(x, datetime.datetime)):
            return str(x)
        if isinstance(x, dict):
            items = ((sort_key(key), sort_key(value)) for (key, value) in x.items())
            return sorted(items)
        return x
    def inner(expected, observed, path='root'):
        if (isinstance(expected, dict) and isinstance(observed, dict)):
            self.assertEqual(len(expected), len(observed), ('path:    %s.    Dict    lengths    are    not    equal' % path))
            expected_keys = sorted(expected)
            observed_keys = sorted(observed)
            self.assertEqual(expected_keys, observed_keys, ('path:    %s.    Dict    keys    are    not    equal' % path))
            for key in list(six.iterkeys(expected)):
                inner(expected[key], observed[key], (path + ('.%s' % key)))
        elif (isinstance(expected, (list, tuple, set)) and isinstance(observed, (list, tuple, set))):
            self.assertEqual(len(expected), len(observed), ('path:    %s.    List    lengths    are    not    equal' % path))
            expected_values_iter = iter(sorted(expected, key=sort_key))
            observed_values_iter = iter(sorted(observed, key=sort_key))
            for i in range(len(expected)):
                inner(next(expected_values_iter), next(observed_values_iter), (path + ('[%s]' % i)))
        else:
            self.assertEqual(expected, observed, ('path:    %s' % path))
    try:
        inner(expected, observed)
    except testtools.matchers.MismatchError as e:
        difference = e.mismatch.describe()
        if message:
            message = ('message:    %s\n' % message)
        msg = ('\nexpected:\n%s\nactual:\n%s\ndifference:\n%s\n%s' % (pprint.pformat(expected), pprint.pformat(observed), difference, message))
        error = AssertionError(msg)
        error.expected = expected
        error.observed = observed
        error.difference = difference
        raise error
