-2

I've been struggling to find a solution for this problem. I feel like it can be done recursively, but I haven't been able to think of a way to do it.

I have a list of keys and values. The keys are subcategorized by dots.

[
    {"key": "global.person.name", "value": "john doe"},
    {"key": "global.person.age", "value": "20"},
    {"key": "global.name", "value": "my program"},
    {"key": "program", "value": "test01"},
    {"key": "active", "value": "true"}
]

And I'm trying to map to a dictionary structure similar to this:

{
    "global":{
        "name": "my program"
        "person":{
            "name": "john doe",
            "age": "20"
        }
    },
    "program": "test01",
    "active": "true"
}

How could I achieve that?

davis
  • 1,216
  • 5
  • 14
  • 27
  • 2
    What did you try? – Pranav Hosangadi Apr 14 '22 at 22:36
  • Related: https://stackoverflow.com/questions/49427002/pythonic-way-to-manipulate-nested-dict-from-dotted-path https://stackoverflow.com/questions/57560308/python-set-dictionary-nested-key-with-dot-delineated-string https://stackoverflow.com/questions/51876885/how-to-set-attribute-on-an-object-given-a-dotted-path – SuperStormer Apr 14 '22 at 22:38

2 Answers2

0

It's not pretty. But it works.

Given more time I would make this recursively obviously...

data = [
    {"key": "global.person.name", "value": "john doe"},
    {"key": "global.person.age", "value": "20"},
    {"key": "global.name", "value": "my program"},
    {"key": "program", "value": "test01"},
    {"key": "active", "value": "true"}
]

new_structure = {}
for dict in data:
    key = dict["key"].split(".")
    if len(key) == 1:
        new_structure[key[0]] = dict["value"]
        continue
    else:
        if key[0] not in new_structure:
            new_structure[key[0]] = {}
        if key[1] not in new_structure[key[0]]:
            new_structure[key[0]][key[1]] = {}

    if len(key) == 2:
        new_structure[key[0]][key[1]] = dict["value"]
    elif len(key) == 3:
        new_structure[key[0]][key[1]][key[2]] = dict["value"]
0

Here is a iterative solution:

data = [
    {"key": "global.person.name", "value": "john doe"},
    {"key": "global.person.age", "value": "20"},
    {"key": "global.name", "value": "my program"},
    {"key": "program", "value": "test01"},
    {"key": "active", "value": "true"}
]

result = {}

for item in data:
    current = result
    *key_parts, last_key_part = item["key"].split('.')
    for key_part in key_parts:
        current = current.setdefault(key_part, {})

    current[last_key_part] = item["value"]

print(result)

I'm taking advantage of iterable unpacking to in the line *key_parts, last_key_part = item["key"].split('.') to more concisely write code that puts all but the last part of the key into key_parts, and the last part into last_key_part. The dict.setdefault method is useful when you want to access an item of a dict or create a new entry in the dict if the key is not present.


Alternatively, taking inspiration from an answer to a similar question, it can be made recursive:

def add_item(obj, key, value):
    first, match, rest = key.partition('.')
    if rest:
        # recurse
        add_item(obj.setdefault(first, {}), rest, value)
    else:
        # this is the last part of the key, so use it to enter the value rather than recursing
        obj[first] = value


result = {}

for item in data:
    add_item(result, item["key"], item["value"])

print(result)

This version is equivalent to the iterative version, just written differently. It is different from the answer I used as inspiration since it doesn't need to deal with list indices but it does need to be able to create new entries in dictionaries rather than just modify existing values.

Oli
  • 2,507
  • 1
  • 11
  • 23