def assertJSONEqual(
        self,
        j1: serial_utils.JSON,
        j2: serial_utils.JSON,
        path: Optional[serial_utils.Lens] = None,
        skips: Optional[Set[str]] = None,
        numeric_places: int = 7,
        unordereds: Optional[Set[str]] = None,
        unordered: bool = False,
    ) -> None:
        """Assert equality between JSON-like `j1` and `j2`.

        The `path` argument is used to track the path to the current object in
        the JSON structure. It is used to provide more informative error
        messages in case of a mismatch. The `skips` argument is used to skip
        certain keys in the comparison. The `numeric_places` argument is used to
        specify the number of decimal places to compare for floating point
        numbers.

        Data types supported for comparison are:
            - JSON-like base types (int, float, str)
            - JSON-like constructors (list, dict)
            - datetime
            - dataclasses
            - pydantic models

        Args:
            j1: The first JSON-like object.

            j2: The second JSON-like object.

            path: The path to the current object in the JSON structure.

            skips: A set of keys to skip in the comparison.

            numeric_places: The number of decimal places to compare for floating
                point numbers.

            unordereds: A set of keys or attribute names whose associated values
                are compared without orderered if they are sequences.

            unordered: If true, the order of elements in a sequence is not
                checked. Note that this only applies to the inputs `j1` and `j2`
                and not to any nested elements.

        Raises:
            AssertionError: If the two JSON-like objects are
                not equal (except for anything skipped) or anything within
                numeric tolerance.
        """

        skips = skips or set([])
        path = path or serial_utils.Lens()
        unordereds = unordereds or set([])

        def recur(j1, j2, path, unordered=False):
            return self.assertJSONEqual(
                j1,
                j2,
                path=path,
                skips=skips,
                numeric_places=numeric_places,
                unordered=unordered,
                unordereds=unordereds,
            )

        ps = str(path)

        if ps in skips:
            return

        self.assertIsInstance(j1, type(j2), ps)

        if isinstance(j1, serial_utils.JSON_BASES):
            if isinstance(j1, (int, float)):
                self.assertAlmostEqual(j1, j2, places=numeric_places, msg=ps)
            else:
                self.assertEqual(j1, j2, ps)

        elif isinstance(j1, Dict):
            ks1 = set(j1.keys())
            ks2 = set(j2.keys())

            with self.subTest("keys", path=ps):
                self.assertSetEqual(ks1, ks2, ps)

            for k in ks1:
                if k in skips or k not in ks2:
                    continue

                with self.subTest(k, path=ps):
                    recur(j1[k], j2[k], path=path[k], unordered=k in unordereds)

        elif isinstance(j1, Sequence):
            self.assertEqual(len(j1), len(j2), ps)

            if unordered:
                j1 = str_sorted(j1, skips=skips)
                j2 = str_sorted(j2, skips=skips)

            for i, (v1, v2) in enumerate(zip(j1, j2)):
                with self.subTest(i, path=ps):
                    recur(v1, v2, path=path[i])

        elif isinstance(j1, datetime):
            self.assertEqual(j1, j2, ps)

        elif is_dataclass(j1):
            for f in fields(j1):
                if f.name in skips:
                    continue

                with self.subTest(f.name, path=ps):
                    self.assertTrue(hasattr(j2, f.name))

                    recur(
                        getattr(j1, f.name),
                        getattr(j2, f.name),
                        path[f.name],
                        unordered=f.name in unordereds,
                    )

        elif isinstance(j1, BaseModel):
            for f in type(j1).model_fields:
                if f in skips:
                    continue

                with self.subTest(f, path=ps):
                    self.assertTrue(hasattr(j2, f))

                    recur(
                        getattr(j1, f),
                        getattr(j2, f),
                        path[f],
                        unordered=f in unordereds,
                    )

        elif isinstance(j1, pydantic.v1.BaseModel):
            for f in j1.__fields__:
                if f in skips:
                    continue

                with self.subTest(f, path=ps):
                    self.assertTrue(hasattr(j2, f))

                    recur(
                        getattr(j1, f),
                        getattr(j2, f),
                        path[f],
                        unordered=f in unordereds,
                    )

        else:
            raise RuntimeError(
                f"Don't know how to compare objects of type {type(j1)} at {ps}."
            )
Assert equality between JSON-like `j1` and `j2`.

        The `path` argument is used to track the path to the current object in
        the JSON structure. It is used to provide more informative error
        messages in case of a mismatch. The `skips` argument is used to skip
        certain keys in the comparison. The `numeric_places` argument is used to
        specify the number of decimal places to compare for floating point
        numbers.

        Data types supported for comparison are:
            - JSON-like base types (int, float, str)
            - JSON-like constructors (list, dict)
            - datetime
            - dataclasses
            - pydantic models

        Args:
            j1: The first JSON-like object.

            j2: The second JSON-like object.

            path: The path to the current object in the JSON structure.

            skips: A set of keys to skip in the comparison.

            numeric_places: The number of decimal places to compare for floating
                point numbers.

            unordereds: A set of keys or attribute names whose associated values
                are compared without orderered if they are sequences.

            unordered: If true, the order of elements in a sequence is not
                checked. Note that this only applies to the inputs `j1` and `j2`
                and not to any nested elements.

        Raises:
            AssertionError: If the two JSON-like objects are
                not equal (except for anything skipped) or anything within
                numeric tolerance.
        
tests/test.py