8

I'm running Python 2.7 and I'm trying to create a custom FloatEncoder subclass of JSONEncoder. I've followed many examples such as this but none seem to work. Here is my FloatEncoder class:

class FloatEncoder(JSONEncoder):
    def _iterencode(self, obj, markers=None):
         if isinstance(obj, float):
            return (str(obj) for obj in [obj])
        return super(FloatEncoder, self)._iterencode(obj, markers)

And here is where I call json.dumps:

with patch("utils.fileio.FloatEncoder") as float_patch:
        for val,res in ((.00123456,'0.0012'),(.00009,'0.0001'),(0.99999,'1.0000'),({'hello':1.00001,'world':[True,1.00009]},'{"world": [true, 1.0001], "hello": 1.0000}')): 
            untrusted = dumps(val, cls=FloatEncoder)
            self.assertTrue(float_patch._iterencode.called)
            self.assertEqual(untrusted, res)

The first assertion fails, meaning that _iterencode is not being executed. After reading the JSON documentation,I tried overriding the default() method but that also was not being called.

1
  • 5
    FWIW, default() is not being called because if the input is one of the types the encoder supports by default, it will not even look at your custom method. Compare lib/json/encoder.py, in the definition of _iterencode(): The _default() is only called in the else: branch, after all known types have been covered. Therefore you cannot override handling for a known type. Commented Aug 11, 2012 at 9:07

2 Answers 2

2

You seem to be trying to round float values down to 4 decimal points while generating JSON (based on test examples).

JSONEncoder shipping with Python 2.7 does not have have _iterencode method, so that's why it's not getting called. Also a quick glance at json/encoder.py suggests that this class is written in such a way that makes it difficult to change the float encoding behavior. Perhaps, it would be better to separate concerns, and round the floats before doing JSON serialization.

EDIT: Alex Martelli also supplies a monkey-patch solution in a related answer. The problem with that approach is that you're introducing a global modification to json library behavior that may unwittingly affect some other piece of code in your application that was written with assumption that floats were encoded without rounding.

Try this:

from collections import Mapping, Sequence
from unittest import TestCase, main
from json import dumps

def round_floats(o):
    if isinstance(o, float):
        return round(o, 4)
    elif isinstance(o, basestring):
        return o
    elif isinstance(o, Sequence):
        return [round_floats(item) for item in o]
    elif isinstance(o, Mapping):
        return dict((key, round_floats(value)) for key, value in o.iteritems())
    else:
        return o

class TestFoo(TestCase):
    def test_it(self):
        for val, res in ((.00123456, '0.0012'),
                         (.00009, '0.0001'),
                         (0.99999, '1.0'),
                         ({'hello': 1.00001, 'world': [True, 1.00009]},
                          '{"world": [true, 1.0001], "hello": 1.0}')):
            untrusted = dumps(round_floats(val))
            self.assertEqual(untrusted, res)

if __name__ == '__main__':
    main()
Sign up to request clarification or add additional context in comments.

Comments

0

Don't define _iterencode, define default, as shown in the third answer on that page.

1 Comment

I did try that as well but it's the same problem: the default() method is not being called.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.