How to convert a MultiDict to nested dictionary
Asked Answered
T

3

11

I would like to convert a POST from Webob MultiDict to nested dictionary. E.g.

So from a POST of:

'name=Kyle&phone.number=1234&phone.type=home&phone.number=5678&phone.type=work'

to a multidict;

[('name', 'Kyle'), ('phone.number', '1234'), ('phone.type', 'home'), ('phone.number', '5678'), ('phone.type', 'work')]

to a nested dictionary

{'name': 'Kyle',
 'phone': [
  {
    'number': '12345',
    'type': 'home',
  },{
    'number': '5678',
    'type': 'work',
  },

Any ideas?

EDIT

I ended up extracting the variable_decode method from the formencode package as posted by Will. The only change that was required is to make the lists explicit, E.g.

'name=Kyle&phone-1.number=1234&phone-1.type=home&phone-2.number=5678&phone-2.type=work'

Which is better for many reasons.

Tubulate answered 30/1, 2012 at 0:52 Comment(1)
Also take a look at Peppercorn from the Pylons project: docs.pylonsproject.org/projects/peppercorn/en/latest it requires being more explicit while building your forms, but does allow arbitrary nesting.Monthly
V
9

If you have formencode installed or can install it, checkout out their variabledecode module

Vaduz answered 30/1, 2012 at 1:15 Comment(1)
I extracted the variable_decode() method and it works, perfectly, thank you.Tubulate
E
1

I haven't had the time to test it and it's quite restrictive, but hopefully this will work (I'm only posting because it's been a while since you posted the question):

>>> def toList(s):
...     answer = []
...     L = s.split("&")
...     for i in L:
...             answer.append(tuple(i.split('=')))
...     return answer

>>> def toDict(L):
...     answer = {}
...     answer[L[0][0]] = L[0][1]
...     for i in L[1:]:
...             pk,sk = L[i][0].split('.')
...             if pk not in answer:
...                     answer[pk] = []
...             if sk not in answer[pk][-1]:
...                     answer[pk][sk] = L[i][1]
...             else:
...                     answer[pk].append({sk:L[i][1]})

If this is not 100%, it should at least get you on a good start.

Hope this helps

Errant answered 30/1, 2012 at 3:34 Comment(1)
Thank you for your response, it gave me a better understand of a limitation I had created by not making Lists explicit. It's difficult to know when to create a list unless it's indicated in the key name.Tubulate
U
1

I prefer an explicit way to solve your problem:

  1. Divide the members which belong to the same structure (or dict) into a same group with same field name, like

    'name=Kyle&phone1=1234&phone1=home&phone2=5678&phone2=work'
    
  2. The order of the fields in the form is guaranteed, so the multidict will be: (('name', 'Kyle'), ('phone1', '1234', 'home'), ('phone2', '5678', 'work'))

  3. Then the code will be like:

    def extract(key, values):
        extractor = {
           "name":str,
           "phone":lambda *args:dict(zip(('number', 'type'), args)
        }
        trimed_key = re.match(r"^(\w+)", key).group(1)
        return trimed_key, extractor(trimed_key, *values)
    
    nested_dict = {}
    for i in multidict():
        key, values = i[0], i[1:]
        nested_dict.setdefault(key, [])
        trimed_key, data_wanted = extract(key, values) 
        nested_dict[trimed_key].append(data_wanted)
    
    for key in nested_dict:
        if len(nested_dict[key]) == 1:
           nested_dict[key] = nested_dict[key][0]
    
Unrivaled answered 30/1, 2012 at 4:29 Comment(1)
Thank you for the response. Explicit is the way to solve this. I ended using the variable_decode() method from the formencode package as posted by Will.Tubulate

© 2022 - 2024 — McMap. All rights reserved.