diff --git a/src/polyfills/json/__init__.py b/src/polyfills/json/__init__.py index 8fe6df7..cdeca03 100644 --- a/src/polyfills/json/__init__.py +++ b/src/polyfills/json/__init__.py @@ -15,6 +15,20 @@ __all__ = ["dumps", "dump", "loads", "load"] +def escape_string(string): + # type: (str) -> str + """Escapes a string so that it can be used as a JSON string. + + Args: + string (str): The string to escape. + + Returns: + str: The escaped string. + """ + return string \ + .replace("\\", "\\\\") \ + .replace('"', '\\"') + def dumps( obj, indent=None, # type: int|None @@ -58,7 +72,17 @@ def dumps( if len(obj_string_parts) > 1: obj_string_parts[-1] += ", " value = dumps(obj[key], indent_spaces, truthy_value, falsy_value) - obj_string_parts.append('"%s": %s' % (str(key), str(value))) + parsed_key = dumps(key) + + # Remove quotes from the parsed key, because we will add them + # manually later. + if parsed_key.startswith('"') and parsed_key.endswith('"'): + parsed_key = parsed_key[1:-1] + + obj_string_parts.append('"%s": %s' % ( + parsed_key, + str(value), + )) obj_string_parts.append("}") #endif @@ -111,7 +135,7 @@ def dumps( # ---> Base types elif obj_type == type(""): - return '"%s"' % str(obj).replace("\\", "\\\\").replace('"', '\\"') + return '"%s"' % escape_string(str(obj)) elif obj_type == type(5): return str(obj) elif obj_type == type(5.0): diff --git a/src/polyfills/json/tests/dumps/test_dumps.py b/src/polyfills/json/tests/dumps/test_dumps.py index 3d6a304..546a8e6 100644 --- a/src/polyfills/json/tests/dumps/test_dumps.py +++ b/src/polyfills/json/tests/dumps/test_dumps.py @@ -117,6 +117,43 @@ def test_simple(self): '{"key": "value"}' ) + def test_conversion_keys(self): + self.assertEqual( + json.dumps({True: 1, False: 0, None: "null"}), + '{"true": 1, "false": 0, "null": "null"}' + ) + + def test_quotes(self): + """ Quotes should be escaped (see #15) """ + self.assertEqual( + json.dumps({"\"": "\""}), + r'{"\"": "\""}', + "Double quotes should be escaped both in keys and values" + ) + self.assertEqual( + json.dumps({"'": "'"}), + r"""{"'": "'"}""", + "Single quotes should be kept as-is", + ) + + + def test_escape(self): + self.assertEqual( + json.dumps({"key": "va'lue"}), + r"""{"key": "va'lue"}""", + "Single quotes do not need to be escaped" + ) + self.assertEqual( + json.dumps({'k"ey': 'va"lue'}), + r"""{"k\"ey": "va\"lue"}""", + "Double quotes should be escaped" + ) + self.assertEqual( + json.dumps({r"k\ey": r"va\lue"}), + r"""{"k\\ey": "va\\lue"}""", + "Backslashes should be escaped both in keys and values" + ) + def test_complex(self): if str(1==1) == 'True': __true__ = 1==1