diff --git a/hjson/decoder.py b/hjson/decoder.py index fbcc2a2..1d16390 100644 --- a/hjson/decoder.py +++ b/hjson/decoder.py @@ -253,7 +253,12 @@ def scantfnns(context, s, end): integer, frac, exp = m.groups() if frac or exp: res = context.parse_float(integer + (frac or '') + (exp or '')) - if int(res) == res and abs(res)<1e10: res = int(res) + # Only collapse to int when the exponent is what made the + # value a whole number (e.g. "17.01e2" -> 1701). A plain + # decimal literal like "1.0" or "10.00" was written as a + # float on purpose and should stay one, matching what + # dumps() produces for it and what json.loads() would do. + if exp and int(res) == res and abs(res)<1e10: res = int(res) else: res = context.parse_int(integer) return res, end diff --git a/hjson/tests/assets/comments_result.hjson b/hjson/tests/assets/comments_result.hjson index a99ce23..1060d06 100644 --- a/hjson/tests/assets/comments_result.hjson +++ b/hjson/tests/assets/comments_result.hjson @@ -9,7 +9,7 @@ rem2: "// test" rem3: "/* test */" num1: 0 - num2: 0 + num2: 0.0 num3: 2 true1: true true2: true diff --git a/hjson/tests/assets/comments_result.json b/hjson/tests/assets/comments_result.json index e247803..ca4076d 100644 --- a/hjson/tests/assets/comments_result.json +++ b/hjson/tests/assets/comments_result.json @@ -9,7 +9,7 @@ "rem2": "// test", "rem3": "/* test */", "num1": 0, - "num2": 0, + "num2": 0.0, "num3": 2, "true1": true, "true2": true, diff --git a/hjson/tests/test_decode.py b/hjson/tests/test_decode.py index cdab0ef..14abe8f 100644 --- a/hjson/tests/test_decode.py +++ b/hjson/tests/test_decode.py @@ -24,6 +24,30 @@ def test_float(self): self.assertTrue(isinstance(rval, float)) self.assertEqual(rval, 1.0) + def test_decimal_point_is_not_dropped(self): + # A literal that was written with a decimal point should stay a + # float even when its fractional part is all zeros, since the + # author explicitly chose the float form. Silently turning it + # into an int both loses that distinction and breaks round + # tripping: dumps(1.0) already produces "1.0", so loads("1.0") + # ought to hand back a float rather than an int. + for text, expected in ( + ("1.0", 1.0), + ("10.00", 10.0), + ("-3.0", -3.0), + ): + rval = json.loads(text) + self.assertTrue(isinstance(rval, float), (text, rval)) + self.assertEqual(rval, expected) + + def test_exponent_can_still_collapse_to_int(self): + # When it's the exponent that turns the value into a whole + # number, keeping it an int is fine: the decimal point in the + # source wasn't the last word on the value's magnitude. + rval = json.loads("17.01e2") + self.assertTrue(isinstance(rval, int)) + self.assertEqual(rval, 1701) + def test_decoder_optimizations(self): # Several optimizations were made that skip over calls to # the whitespace regex, so this test is designed to try and