Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

urlencode a multidimensional dictionary in python

How can I get a URL-encoded version of a multidimensional dictionary in Python? Unfortunately, urllib.urlencode() only works in a single dimension. I would need a version capable of recursively encoding the dictionary.

For example, if I have the following dictionary:

{'a': 'b', 'c': {'d': 'e'}}

I want to obtain the following string:

a=b&c[d]=e
like image 930
pablobm Avatar asked Oct 25 '10 10:10

pablobm


4 Answers

I think the code below may be what you want

import urllib.parse

def url_encoder(params):
    g_encode_params = {}

    def _encode_params(params, p_key=None):
        encode_params = {}
        if isinstance(params, dict):
            for key in params:
                encode_key = '{}[{}]'.format(p_key,key)
                encode_params[encode_key] = params[key]
        elif isinstance(params, (list, tuple)):
            for offset,value in enumerate(params):
                encode_key = '{}[{}]'.format(p_key, offset)
                encode_params[encode_key] = value
        else:
            g_encode_params[p_key] = params

        for key in encode_params:
            value = encode_params[key]
            _encode_params(value, key)

    if isinstance(params, dict):
        for key in params:
            _encode_params(params[key], key)

    return urllib.parse.urlencode(g_encode_params)

if __name__ == '__main__':
    params = {'name': 'interface_name', 'interfaces': [{'interface': 'inter1'}, {'interface': 'inter2'}]}
    print(url_encoder(params)) 

the output is

interfaces%5B1%5D%5Binterface%5D=inter2&name=interface_name&interfaces%5B0%5D%5Binterface%5D=inter1

which is look like

interfaces[1][interface]=inter2&name=interface_name&interfaces[0][interface]=inter1

PS: you may want use OrderDict to replace dict above

like image 63
Tony_Wang Avatar answered Nov 17 '22 21:11

Tony_Wang


Based on the code of @malaney, I think that the code below emulates the PHP function http_build_query() quite well.

#!/usr/bin/env python3

import urllib.parse

def http_build_query(data):
    parents = list()
    pairs = dict()

    def renderKey(parents):
        depth, outStr = 0, ''
        for x in parents:
            s = "[%s]" if depth > 0 or isinstance(x, int) else "%s"
            outStr += s % str(x)
            depth += 1
        return outStr

    def r_urlencode(data):
        if isinstance(data, list) or isinstance(data, tuple):
            for i in range(len(data)):
                parents.append(i)
                r_urlencode(data[i])
                parents.pop()
        elif isinstance(data, dict):
            for key, value in data.items():
                parents.append(key)
                r_urlencode(value)
                parents.pop()
        else:
            pairs[renderKey(parents)] = str(data)

        return pairs
    return urllib.parse.urlencode(r_urlencode(data))

if __name__ == '__main__':
    payload = {
        'action': 'add',
        'controller': 'invoice',
        'code': 'debtor',
        'InvoiceLines': [
            {'PriceExcl': 150, 'Description': 'Setupfee'},
            {'PriceExcl':49.99, 'Description':'Subscription'}
        ],
        'date': '2016-08-01',
        'key': 'Yikes&ampersand'
    }
    print(http_build_query(payload))

    payload2 = [
        'item1',
        'item2'
    ]
    print(http_build_query(payload2))
like image 29
LNoor Avatar answered Nov 17 '22 20:11

LNoor


OK people. I implemented it myself:

import urllib

def recursive_urlencode(d):
    """URL-encode a multidimensional dictionary.

    >>> data = {'a': 'b&c', 'd': {'e': {'f&g': 'h*i'}}, 'j': 'k'}
    >>> recursive_urlencode(data)
    u'a=b%26c&j=k&d[e][f%26g]=h%2Ai'
    """
    def recursion(d, base=[]):
        pairs = []

        for key, value in d.items():
            new_base = base + [key]
            if hasattr(value, 'values'):
                pairs += recursion(value, new_base)
            else:
                new_pair = None
                if len(new_base) > 1:
                    first = urllib.quote(new_base.pop(0))
                    rest = map(lambda x: urllib.quote(x), new_base)
                    new_pair = "%s[%s]=%s" % (first, ']['.join(rest), urllib.quote(unicode(value)))
                else:
                    new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
                pairs.append(new_pair)
        return pairs

    return '&'.join(recursion(d))

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Still, I'd be interested to know if there's a better way to do this. I can't believe Python's standard library doesn't implement this.

like image 11
pablobm Avatar answered Nov 17 '22 20:11

pablobm


Something like this?

a = {'a': 'b', 'c': {'d': 'e'}}

url = urllib.urlencode([('%s[%s]'%(k,v.keys()[0]), v.values()[0] ) if type(v)==dict else (k,v) for k,v in a.iteritems()])

url = 'a=b&c%5Bd%5D=e'
like image 3
eumiro Avatar answered Nov 17 '22 21:11

eumiro