python json.JSONEncoder encoder do not match str values but other objects

Question:

Instead of traversing entire structure I hoped to use json.JSONEncoder, but I’m unable to get it to trigger on strings:

#!/usr/bin/env python3
import json
import datetime
class StringShortener(json.JSONEncoder):
    def default(self, obj):
        strl = 10
        if not isinstance(obj, (int,float)) and len(str(obj))>strl:
            return str(obj)[:strl] + f"<<cut at {strl}>>"
        return obj
mystruct = {
    "dummy": "foobar just being more than 10char long",
    "dt": datetime.datetime.now() 
}
# only shortening the td object
print(json.dumps(mystruct, cls=StringShortener))

Gives:

{"dummy": "foobar just being more than 10char long", "dt": "2022-09-22<<cut at 10>>"}

But key ‘dummy’s value also match the stringShortener:

>>> obj = "foobar just being more than 10char long"
>>> bool(not isinstance(obj, (int,float)) and len(str(obj))>10)   
True
>>> str(obj)[:10] + f"<<cut at 10>>"       
'foobar jus<<cut at 10>>'

How can this be fixed so str types also get shortened?

Asked By: MortenB

||

Answers:

Overriding the encode function should do it.

    import json
    import datetime
    
    class StringShortener(json.JSONEncoder):
        def default(self, obj):
            strl = 10
            if not isinstance(obj, (int,float)) and len(str(obj))>strl:
                return str(obj)[:strl] + f"<<cut at {strl}>>"
            return obj
    
        # just override the encode function in the same way as the default function
        def encode(self, o):
            strl = 10
            # This is for extremely simple cases and benchmarks.s
            if isinstance(o, str):
                if self.ensure_ascii:
                    return o[:strl]
                else:
                    return o
           
            # cut the strings if they are too long
            for key, value in o.items():
                if isinstance(value, str):
                    o[key] = value[:strl] + f"<<cut at {strl}>>"
     
            # This doesn't pass the iterator directly to ''.join() because the
            # exceptions aren't as detailed.  The list call should be roughly
            # equivalent to the PySequence_Fast that ''.join() would do.
            chunks = self.iterencode(o, _one_shot=True)
            if not isinstance(chunks, (list, tuple)):
                chunks = list(chunks)
            return ''.join(chunks)
           
    mystruct = {
        "dummy": "foobar just being more than 10char long",
        "dt": datetime.datetime.now() 
    }
    # shortening the td and dummy object
    print(json.dumps(mystruct, cls=StringShortener))

giving:

{"dummy": "foobar jus<<cut at 10>>", "dt": "2022-09-23<<cut at 10>>"}
Answered By: Philipp
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.