1

I need to create a json file , given a dictionary of paths and their values. I written some code for adding an entry, that looks like it functions and result is correct, but as somebody who new in python, I wonder how can this be improved, and if there is a function that does the same, already exist in modules included with python 2.7?

   def path_to_list(path):
        if isinstance(path, (str,)):
            map_list = path.split("/")
            for i, key in enumerate(map_list):
                if key.isdigit():
                    map_list[i] = int(key)
        else:
            map_list = path
        return map_list


def add_to_dictionary(dic, keys, value):
    for i, key in enumerate(keys[:-1]):
        if i < len(keys)-1 and isinstance(keys[i+1], int):
            # Case where current key should be a list, since next key is
            # is list position
            if key not in dic.keys():
                # Case list not yet exist
                dic[keys[i]] = []
                dic[keys[i]].append({})
                dic = dic.setdefault(key, {})
            elif not isinstance(dic[key], list):
                # Case key exist , but not a list
                # TO DO : check how to handle
                print "Failed to insert " + str(keys) + ", trying to insert multiple to not multiple  "
                break
            else:
                # Case where the list exist
                dic = dic.setdefault(key, {})
        elif i < len(keys)-1 and isinstance(key, int):
            # Case where current key is instance number in a list
            try:
                # If this succeeds instance already exist
                dic = dic[key]
            except (IndexError,KeyError):
                # Case where list exist , but need to add new instances  ,
                # as key instance  not exist
                while len(dic)-1 < key:
                    dic.append({})
                dic = dic[key]
        else:
            # Case where key is not list or instance of list
            dic = dic.setdefault(key, {})
    # Update value
    dic[keys[-1]] = value

my_dict1 ={}
add_to_dictionary(my_dict1, path_to_list("a/0/b/c"), 1)
print my_dict1

{'a': [{'b': {'c': 1}}]}

add_to_dictionary(my_dict1, path_to_list("a/2/b/c"), "string")
print my_dict1

{'a': [{'b': {'c': 1}}, {}, {'b': {'c': 'string'}}]}

add_to_dictionary(my_dict1, path_to_list("a/2/b/c"), "new string")
print my_dict1

{'a': [{'b': {'c': 1}}, {}, {'b': {'c': 'new string'}}]}

Some keys may already exist, then I update just the value.

Numeric keys indicate that key before it can have multiple values, and I'm adding/updating value in this place in array

Cyaxares
  • 13
  • 3
  • 1
    where are you facing the issue you can't post the entire code and tell us to figure it out, please create a [MCVE](https://stackoverflow.com/help/mcve) – Kunal Mukherjee Mar 27 '19 at 07:34
  • @Kunal Mukherjee Thanks for response . Functionally , no issues that I know of , but for learning, as somebody who new in python, I want to know if this can be written more elegantly, or even better, if there is already existing module function included with Python 2.7, that does the same – Cyaxares Mar 27 '19 at 07:43
  • Do you need to use lists as intermediate nodes? It makes your structure a bit awkward with inserted empty nodes. A dictionary is effectively a sparse array. There is a nice answer to the question [What is the best way to implement nested dictionaries](https://stackoverflow.com/questions/635483/what-is-the-best-way-to-implement-nested-dictionaries) – Mike Robins Mar 27 '19 at 08:43
  • @Mike Robins Thanks for response. The requirement from output json file is, if as example "a" is key of array type, in must be array in json, like "a":[] , even if it holds no values under it. Each key has predefined type , and paths:values I receive were build accordingly – Cyaxares Mar 27 '19 at 09:50

1 Answers1

0

Here is my implementation of your data structure using nested dictionaries:

class Tree(dict):
    '''http://stackoverflow.com/questions/635483/what-is-the-best-way-to-implement-nested-dictionaries-in-python'''

    def __missing__(d, k):
        v = d[k] = type(d)()
        return v

    def grow(d, path, v):
        ps = map(lambda k: int(k) if k.isdigit() else k, path.split('/'))
        reduce(lambda d, k: d[k], ps[:-1], d)[ps[-1]] = v

Testing this:

t = Tree()
t.grow('a/0/b/c', 1)
print t
t['a'][2]['b']['c'] = 'string'
print t
t.grow('a/2/b/c', 'new_string')
print t

gives:

{'a': {0: {'b': {'c': 1}}}}
{'a': {0: {'b': {'c': 1}}, 2: {'b': {'c': 'string'}}}}
{'a': {0: {'b': {'c': 1}}, 2: {'b': {'c': 'new_string'}}}}

But you want the integer indexed dictionaries to be arrays. The routine below steps through the nested dictionaries turning some into lists. It does some copying so as to not mess up the original nested dictionaries. I'd use this as part of the outout stage only.

import numbers
def keys_all_int(d):
    return reduce(lambda r, k: r and isinstance(k, numbers.Integral), d.keys(), True)

def listify(d):
    '''
    Take a tree of nested dictionaries, and
    return a copy of the tree with sparse lists in place of the dictionaries
    that had only integers as keys.
    '''
    if isinstance(d, dict):
        d = d.copy()
        for k in d:
            d[k] = listify(d[k])
        if keys_all_int(d):
            ds = [{}]*(max(d.keys())+1)
            for k in d:
                ds[k] = d[k]
            return ds
    return d

Testing this:

t = Tree()
t.grow('a/0/b/c', 1)
print listify(t)
t['a'][2]['b']['c'] = 'string'
print listify(t)
t.grow('a/2/b/c', 'new_string')
print listify(t)

Gives:

{'a': [{'b': {'c': 1}}]}
{'a': [{'b': {'c': 1}}, {}, {'b': {'c': 'string'}}]}
{'a': [{'b': {'c': 1}}, {}, {'b': {'c': 'new_string'}}]}

Finally if you are dealing with JSON use the json module:

import json
print json.dumps(listify(t),
    sort_keys=True, indent = 4, separators = (',', ': '))

Gives:

{
    "a": [
        {
            "b": {
                "c": 1
            }
        },
        {},
        {
            "b": {
                "c": "new_string"
            }
        }
    ]
}
Mike Robins
  • 1,585
  • 7
  • 13