Nico Schlömer
Nico Schlömer

Reputation: 58761

write json float in scientific notation

I would like to represent floats in a JSON file in scientific notation using Python 3.6+. None of

import json

a = 0.001234567

print(json.dumps(a))

json.encoder.FLOAT_REPR = lambda x: "{:e}".format(x)
print(json.dumps(a))

json.encoder.c_make_encoder = None
json.encoder.FLOAT_REPR = lambda x: "{:e}".format(x)
print(json.dumps(a))

work: All three prints give

0.001234567

instead of the desired

1.234567e-03

(Note that the last version works at least in in Python 2.7.15rc1.)

The answer should work with lists of floats as well.

Any hints?

Upvotes: 2

Views: 2061

Answers (2)

Nico Schlömer
Nico Schlömer

Reputation: 58761

Eventually I created my own json library that can format floats, fjson. Install with

pip install fjson

and use as

import math
import fjson


data = {"a": 1, "b": math.pi}
string = fjson.dumps(data, float_format=".6e", indent=2, separators=(", ", ": "))
print(string)
{
  "a": 1,
  "b": 3.141593e+00
}

Upvotes: 0

PaulMcG
PaulMcG

Reputation: 63709

You have to add some special casing for dicts, lists, sets, etc., but by referencing abstract base classes from collections.abc, you avoid explicitly testing for specific types.

Note that the test for Sequence has to avoid matching on str types, since iterating over a str gives a bunch of 1-character strs, which are also iterable Sequences, and so on until you reach the recursion limit. I could not find an ABC that represents "a sequence container, but not a str".

(I also have to echo Alex Martelli's criticism from a related post, that having to do this much work just to format a particular type speaks to issues in the design of the classes in this module.)

import json
from collections.abc import Mapping, Sequence

a = 0.001234567

class ScientificNotationEncoder(json.JSONEncoder):
    def iterencode(self, o, _one_shot=False):
        if isinstance(o, float):
            return "{:e}".format(o)
        elif isinstance(o, Mapping):
            return "{{{}}}".format(', '.join('"{}" : {}'.format(str(ok), self.iterencode(ov))
                                             for ok, ov in o.items()))
        elif isinstance(o, Sequence) and not isinstance(o, str):
            return "[{}]".format(', '.join(map(self.iterencode, o)))
        return ', '.join(super().iterencode(o, _one_shot))

aout = json.dumps([a, a, "xyzzy", 42, {'z': a}, (a, a, a),],
                  cls=ScientificNotationEncoder)
print(aout)

# loading back in seems to still work okay!
print(json.loads(aout))

Prints:

[1.234567e-03, 1.234567e-03, "xyzzy", 42, {"z" : 1.234567e-03}, [1.234567e-03, 1.234567e-03, 1.234567e-03]]
[0.001234567, 0.001234567, 'xyzzy', 42, {'z': 0.001234567}, [0.001234567, 0.001234567, 0.001234567]]

Upvotes: 3

Related Questions