Last active
October 15, 2021 23:59
-
-
Save simonw/e42aaa4fc18da5bcceb6 to your computer and use it in GitHub Desktop.
Function for recursively checking that a Python data structure consists only of JSON-compatible primitives.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def is_jsonlike(obj): | |
# Recursively checks if obj is composed | |
# only of JSON-compatible primitives | |
t = type(obj) | |
if t in (str, unicode, int, float, bool, type(None)): | |
return True | |
elif t is dict: | |
for key, value in obj.items(): | |
if type(key) not in (str, unicode): | |
return False | |
if not is_jsonlike(value): | |
return False | |
return True | |
elif t in (list, tuple): | |
return all(is_jsonlike(item) for item in obj) | |
else: | |
return False | |
import unittest | |
class JsonLikeTests(unittest.TestCase): | |
def assertGood(self, obj): | |
self.assert_(is_jsonlike(obj), '%s should pass is_jsonlike' % repr(obj)) | |
def assertBad(self, obj): | |
self.assert_(not is_jsonlike(obj), '%s should not pass is_jsonlike' % repr(obj)) | |
def test_primitives(self): | |
self.assertGood(1) | |
self.assertGood(1.5) | |
self.assertGood(-1) | |
self.assertGood(0) | |
self.assertGood(None) | |
self.assertGood('string') | |
self.assertGood(u'unicode') | |
self.assertGood(True) | |
self.assertGood(False) | |
def test_some_bad_objects(self): | |
import datetime | |
self.assertBad(datetime.datetime.now()) | |
self.assertBad(datetime.date.today()) | |
self.assertBad(object) | |
self.assertBad(set()) | |
def test_simple_dict(self): | |
self.assertGood({"foo": "bar"}) | |
self.assertGood({"foo": 1}) | |
self.assertGood({u"bar": 1.5}) | |
def test_dict_with_bad_keys(self): | |
# JSON-like dictionaries must have string keys | |
self.assertBad({1: "foo"}) | |
self.assertBad({1.5: "foo"}) | |
self.assertBad({1: "foo"}) | |
self.assertBad({("tuple", "here"): "foo"}) | |
def test_simple_list(self): | |
self.assertGood([]) | |
self.assertGood([1, 2.5, "three", None, False, True, u'unicode']) | |
def test_simple_tuple(self): | |
self.assertGood(tuple()) | |
self.assertGood((1, 2.5, "three", None, False, True, u'unicode')) | |
def test_complex_nested_object(self): | |
self.assertGood({ | |
"nested": { | |
"tuple": (1, 3, 4.5, {"nested": "again"}), | |
"list": (1, 3, 4.5, {"another": {"nested": True}}), | |
} | |
}) | |
self.assertBad({ | |
"nested": { | |
"tuple": (1, 3, 4.5, {"nested": "again"}), | |
"list": (1, 3, 4.5, {"another": {"nested": True, "bad": object}}), | |
} | |
}) | |
def test_subclasses_not_allowed(self): | |
# It's important people can't sneak subclasses | |
# of basic classes past the is_jsonlike function | |
class mystr(str): | |
pass | |
class myint(int): | |
pass | |
class mydict(dict): | |
pass | |
self.assertBad(mystr("hello")) | |
self.assertBad(myint(1)) | |
self.assertBad({"foo": mydict({"bar": 1})}) | |
if __name__ == '__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment