'Truncate long strings inside json in python

Given some json

[
    {
        "default_value": "True",
        "feature_name": "feature_1",
        "active_on": "remote_1,remote_2,remote_3,remote_4,remote_5,remote_6,remote_7"
    },
    {
        "feature_name": "special_super_duper_feature_wooooooooot",
        "active_on": "remote_2"
    }
]

how do I truncate values longer than, say, 20 chars:

[
    {
        "default_value": "True",
        "feature_name": "feature_1",
        "active_on": "remote_1,remote_2..."
    },
    {
        "feature_name": "special_super_dup...",
        "active_on": "remote_2"
    }
]

as generically as possible?

EDIT: Here's a more generic example to fit:

[
    {
        "a": {"b": "c"},
        "d": "e"
    },
    {
        "a": [{"b": "dugin-walrus-blowing-up-the-view-and-ruining-page-frame"}]
    }
]

The endgame here is to make "pretty-print" for arbitrary json. I'm wondering whether there's a nice way to do that using only standard library.



Solution 1:[1]

Here's my take on it:

import collections
...

def truncate_strings(obj: collections.abc.Iterable, truncate: int, suffix: str = "...") -> int:
    """
    @param obj: generic iterable  - object to truncate. Implemented for dicts and lists. 
                                    Extensible by adding new types to isinstance.
    @param truncate: int          - total str length to be set. value 0 to disable.
    @param suffix: str [Optional] - the truncation string suffix

    @returns count: int           - number of strings truncated 
    """

    if not truncate or not obj or isinstance(obj, str):
        return 0
    count = 0
    if isinstance(obj, dict):
        for key_ in obj.keys():
            if not key_:
                return 0
            if isinstance(obj.get(key_), str):
                if len(obj[key_]) > truncate - len(suffix):
                    count += 1
                    obj[key_] = obj[key_][:truncate - len(suffix)] + suffix
            else:
                count += truncate_strings(key_, truncate, suffix)
    elif isinstance(obj, collections.abc.Iterable):
        for item in obj:
            count += truncate_strings(item, truncate, suffix)
    return count

Note this func is recursive on iterables, so a long list will kill your call stack. Should work on reasonable size jsons though (tested on 1k items json array)

Solution 2:[2]

You can work with a string limiter this way:

[:17] + '...'

And work from loop in your values to readjust its values.

Example:

a = 'test text to work with limiter length'
a = a[:17] + '...'
print(a)

Result:

test text to work...

Solution 3:[3]

I'm not aware of any built-in method to do this, but one approach might be to just iterated over the list, then over the items in each dictionary, and apply a string slice to each item, like so:

def truncate(d: dict):
    for k, v in d.items():
        d.update({k: str(v)[:17] + "..."})
    return d

json_trunc = list(map(lambda x: truncate(x), json_orig))

It would definitely be possible to include the truncating function in the list comprehension too if desired, I've just separated them here for readability / understandability.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Toma
Solution 2 Digital Farmer
Solution 3