How to print integers as hex strings using json.du

2020-07-11 08:28发布

问题:

Currently I am using the following code to print a large data structure

print(json.dumps(data, indent=4))

I would like to see all the integers that get printed in hex instead of decimal. Is that possible? It seems that there is no way to override the existing encoder for integers. You can only provide a default for types not already handled by the JSONEncoder class, but no way to override how it encodes integers.

I figured out I can override the default integer printing behavior using sys.displayhook if I was running in the command line but I am not.

Just for reference the data structure is a mix bag of dicts, lists, strings, ints, etc. So that is why I went with the json.dumps(). The only other way I can think of doing it is to parse it myself and then I would be re-writing the json module.

Update: So I ended up implementing it with serializing functions that just print a copy of the original data structure with all integer types converted to hex strings:

def odprint(self, hexify=False):
    """pretty print the ordered dictionary"""
    def hexify_list(data):
        _data = []
        for i,v in enumerate(data):
            if isinstance(v, (int,long)):
                _data.insert(i,hex(v))
            elif isinstance(v,list):
                _data.insert(i, hexify_list(v))
            else:
                _data.insert(i, val)
        return _data

    def hexify_dict(data):
        _data = odict()
        for k,v in data.items():
            if isinstance(v, (dict,odict)):
                _data[k] = hexify_dict(v)
            elif isinstance(v, (int, long)):
                _data[k] = hex(v)
            elif isinstance(v,list):
                _data[k] = hexify_list(v)
            else:
                _data[k] = v
        return _data

    if hexify:
        print(json.dumps(hexify_dict(self), indent=4))
    else:
        print(json.dumps(self, indent=4))

Thanks for the help. I realize that I end up making an odict from a standard dict, but its just for printing so its fine for what I need.

回答1:

A possible approach is to have a serialize function, which produces a copy of your dictionary on the fly and uses the standard json module to dump the string. A preliminary implementation looks like:

import json

def serialize(data):
    _data = {}
    for k, v in data.items():
        if isinstance(v, int):
            _data[k] = hex(v)
        else:
            _data[k] = v
    return json.dumps(_data, indent=4)


if __name__ == "__main__":
    data = {"a":1, "b":2.0, "c":3}
    print serialize(data)

output:

{
    "a": "0x1", 
    "c": "0x3", 
    "b": 2.0
}

Notice that this preliminary implementation does not work with lists, but this is easily changed.

Some may claim that the approach is memory-intensive because it creates a copy of the original data. This may be the case, but if your data structure is that big, then maybe you should (a) not be using JSON, or (b) create a copy of the JSON module in your working directory and tailor it to your needs.

Cheers.



回答2:

Octal and hexadecimal formats are not supported in JSON.

You could use YAML instead.

>>> import json, yaml
>>> class hexint(int):
...     def __str__(self):
...         return hex(self)
...
>>> json.dumps({"a": hexint(255)})
'{"a": 0xff}'
>>> yaml.load(_)
{'a': 255}

Or without wrapping integers:

import yaml

def hexint_presenter(dumper, data):
    return dumper.represent_int(hex(data))
yaml.add_representer(int, hexint_presenter)

print yaml.dump({"a": 255}), # -> {a: 0xff}
assert yaml.load('{a: 0xff}') == {"a": 255}


回答3:

You can't override the existing encoder for integers...but there might be another way to get what you want. What about something like this:

import json
import re

data = {'test': 33, 'this': 99, 'something bigger':[1,2,3, {'a':44}]}  
s = json.dumps(data, indent=4)
print(re.sub('(\d+)', lambda i: hex(int(i.group(0))),s))

Results in:

{
    "test": 0x21,
    "this": 0x63,
    "something bigger": [
        0x1,
        0x2,
        0x3,
        {
            "a": 0x2c
        }
    ]
}

Note: This isn't especially "robust" (fails on numbers embedded in strings, floats, etc.), but might be good enough for what you want (You could also enhance the regex here so it would work in a few more cases).



回答4:

Dirty hack for Python 2.7, I wouldn't recomend to use it:

import __builtin__

_orig_str = __builtin__.str

def my_str(obj):
    if isinstance(obj, (int, long)):
        return hex(obj)
    return _orig_str(obj)
__builtin__.str = my_str

import json 

data = {'a': [1,2,3], 'b': 4, 'c': 16**20}
print(json.dumps(data, indent=4))

Output:

{
    "a": [
        0x1,
        0x2,
        0x3
    ],
    "c": 0x100000000000000000000L,
    "b": 0x4
}

On Python 3 __builtin__ module is now builtins, but I can't test it (ideone.com fails with ImportError: libz.so.1 ...)



回答5:

You could always reparse the json, where you do have some control over int parsing, so that you can override int repr:

class hexint(int):
   def __repr__(self):
     return "0x%x" % self

json.loads(json.dumps(data), parse_int=hexint)

And using the data as in Gerrat's answer, the output is:

{u'test': 0x21, u'this': 0x63, u'something bigger': [0x1, 0x2, 0x3, {u'a': 0x2c}]}


回答6:

One-liner

If you don't mind your hex strings quoted, use this one-liner:

print(json.dumps(eval(str(json.loads(json.dumps(data), parse_int=lambda i:hex(int(i))))), indent=4))

Output (using Gerrat's data again):

{
    "test": "0x21", 
    "this": "0x63", 
    "something bigger": [
        "0x1", 
        "0x2", 
        "0x3", 
        {
            "a": "0x2c"
        }
    ]
}

This is a better answer than my previous post as I've dealt with getting a pretty-printed result.