diff --git a/README.md b/README.md index 73b3922..827ccc1 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ to activate it. ## Version History / Release Notes: * Unreleased + * Preserve duck-typed mapping contents without testing their truth value. * Preserve duck-typed array contents without testing their truth value. * Honor explicit false-valued callable parsing hooks and default encoders without testing their truth value. diff --git a/json5/lib.py b/json5/lib.py index 048ccbc..f36003c 100644 --- a/json5/lib.py +++ b/json5/lib.py @@ -871,9 +871,6 @@ def _encode_non_basic_type(self, obj, seen: Set, level: int) -> str: return s def _encode_dict(self, obj: Any, seen: set, level: int) -> str: - if not obj: - return '{}' - indent_str, end_str = self._spacers(level) item_sep = self.item_separator + indent_str kv_sep = self.kv_separator diff --git a/tests/lib_test.py b/tests/lib_test.py index 03a80f1..50f812b 100644 --- a/tests/lib_test.py +++ b/tests/lib_test.py @@ -747,6 +747,61 @@ def __bool__(self): # pragma: no cover ) self.assertEqual(json5.loads(result), values) + def test_duck_mapping_without_truth_value(self): + class Mapping: + def __init__(self, values): + self.values = values + + def keys(self): + return iter(self.values) + + def __getitem__(self, key): + return self.values[key] + + def __bool__(self): + raise ValueError( + 'The truth value is undefined' + ) # pragma: no cover + + for values in ({}, {'b': 0, 'a': [1, 2]}): + for indent in (None, 0, 2, '\t'): + for trailing_commas in (False, True): + for sort_keys in (False, True): + with self.subTest( + values=values, + indent=indent, + trailing_commas=trailing_commas, + sort_keys=sort_keys, + ): + options = { + 'indent': indent, + 'trailing_commas': trailing_commas, + 'sort_keys': sort_keys, + } + self.assertEqual( + json5.dumps( + Mapping(values), + use_duck_typing=True, + **options, + ), + json5.dumps(values, **options), + ) + + def test_false_valued_duck_mapping(self): + class Mapping: + def keys(self): + return ['answer'] + + def __getitem__(self, key): + return {'answer': 42}[key] + + def __bool__(self): + return False # pragma: no cover + + self.assertEqual( + json5.dumps(Mapping(), use_duck_typing=True), '{answer: 42}' + ) + def test_invalid_collection(self): # Check that something that isn't actually an array or a dict doesn't # work. @@ -785,9 +840,6 @@ def keys(self): def __getitem__(self, k): return {'a': 1, 'b': 2}[k] - def __len__(self): - return 2 - value = MyDict() self.assertRaises(TypeError, json5.dumps, value) self.assertEqual(