Fixed serialization for custom ints and floats.
[GitHub issue #57](https://github.com/dpranke/pyjson5/issues/57)
Fixed serialization for objects that subclass `int` or `float`:
Previously we would use the objects __str__ implementation, but
that might result in an illegal JSON5 value if the object had
customized __str__ to return something illegal. Instead,
we follow the lead of the `JSON` module and call `int.__repr__`
or `float.__repr__` directly.
While I was at it, I added tests for dumps(-inf) and dumps(nan)
when those were supposed to be disallowed by `allow_nan=False`.
diff --git a/README.md b/README.md
index 7c54fa7..ec846d1 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,16 @@
## Version History / Release Notes
+* v0.9.9 (2022-08-01)
+ * [GitHub issue #57](https://github.com/dpranke/pyjson5/issues/57)
+ Fixed serialization for objects that subclass `int` or `float`:
+ Previously we would use the objects __str__ implementation, but
+ that might result in an illegal JSON5 value if the object had
+ customized __str__ to return something illegal. Instead,
+ we follow the lead of the `JSON` module and call `int.__repr__`
+ or `float.__repr__` directly.
+ * While I was at it, I added tests for dumps(-inf) and dumps(nan)
+ when those were supposed to be disallowed by `allow_nan=False`.
* v0.9.8 (2022-05-08)
* [GitHub issue #47](https://github.com/dpranke/pyjson5/issues/47)
Fixed error reporting in some cases due to how parsing was handling
diff --git a/json5/lib.py b/json5/lib.py
index e56e825..6b3056e 100644
--- a/json5/lib.py
+++ b/json5/lib.py
@@ -264,15 +264,36 @@
s = u'false'
elif obj is None:
s = u'null'
+ elif obj == math.inf:
+ if allow_nan:
+ s = u'Infinity'
+ else:
+ raise ValueError()
+ elif obj == -math.inf:
+ if allow_nan:
+ s = u'-Infinity'
+ else:
+ raise ValueError()
+ elif isinstance(obj, float) and math.isnan(obj):
+ if allow_nan:
+ s = u'NaN'
+ else:
+ raise ValueError()
elif isinstance(obj, str_types):
if (is_key and _is_ident(obj) and not quote_keys
and not _is_reserved_word(obj)):
return True, obj
return True, _dump_str(obj, ensure_ascii)
- elif isinstance(obj, float):
- s = _dump_float(obj,allow_nan)
elif isinstance(obj, int):
- s = str(obj)
+ # Subclasses of `int` and `float` may have custom
+ # __repr__ or __str__ methods, but the `JSON` library
+ # ignores them in order to ensure that the representation
+ # are just bare numbers. In order to match JSON's behavior
+ # we call the methods of the `float` and `int` class directly.
+ s = int.__repr__(obj)
+ elif isinstance(obj, float):
+ # See comment above for int
+ s = float.__repr__(obj)
else:
s = None
@@ -403,20 +424,6 @@
end_str + u']')
-def _dump_float(obj, allow_nan):
- if allow_nan:
- if math.isnan(obj):
- return 'NaN'
- if obj == float('inf'):
- return 'Infinity'
- if obj == float('-inf'):
- return '-Infinity'
- elif math.isnan(obj) or obj == float('inf') or obj == float('-inf'):
- raise ValueError('Out of range float values '
- 'are not JSON compliant')
- return str(obj)
-
-
def _dump_str(obj, ensure_ascii):
ret = ['"']
for ch in obj:
diff --git a/json5/version.py b/json5/version.py
index ed8c2f6..0a39014 100644
--- a/json5/version.py
+++ b/json5/version.py
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-VERSION = '0.9.8'
+VERSION = '0.9.9'
diff --git a/tests/lib_test.py b/tests/lib_test.py
index a6d2473..68842e7 100644
--- a/tests/lib_test.py
+++ b/tests/lib_test.py
@@ -347,13 +347,20 @@
self.assertEqual(json5.dumps(MyArray()), '[0, 1, 1]')
def test_custom_numbers(self):
+ # See https://github.com/dpranke/pyjson5/issues/57: we
+ # need to ensure that we use the bare int.__repr__ and
+ # float.__repr__ in order to get legal JSON values when
+ # people have custom subclasses with customer __repr__ methods.
+ # (This is what JSON does and we want to match it).
class MyInt(int):
- pass
+ def __repr__(self):
+ return 'fail'
self.assertEqual(json5.dumps(MyInt(5)), '5')
class MyFloat(float):
- pass
+ def __repr__(self):
+ return 'fail'
self.assertEqual(json5.dumps(MyFloat(0.5)), '0.5')
@@ -427,6 +434,10 @@
self.assertRaises(ValueError, json5.dumps,
float('inf'), allow_nan=False)
+ self.assertRaises(ValueError, json5.dumps,
+ float('-inf'), allow_nan=False)
+ self.assertRaises(ValueError, json5.dumps,
+ float('nan'), allow_nan=False)
def test_null(self):
self.check(None, 'null')