Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple way to convert a string to a dictionary

What is the simplest way to convert a string of keyword=values to a dictionary, for example the following string:

name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"

to the following python dictionary:

{'name':'John Smith', 'age':34, 'height':173.2, 'location':'US', 'avatar':':,=)'}

The 'avatar' key is just to show that the strings can contain = and , so a simple 'split' won't do. Any ideas? Thanks!

like image 583
astrofrog Avatar asked Nov 29 '09 02:11

astrofrog


3 Answers

This works for me:

# get all the items
matches = re.findall(r'\w+=".+?"', s) + re.findall(r'\w+=[\d.]+',s)

# partition each match at '='
matches = [m.group().split('=', 1) for m in matches]

# use results to make a dict
d = dict(matches)
like image 161
twneale Avatar answered Sep 29 '22 08:09

twneale


Edit: since the csv module doesn't deal as desired with quotes inside fields, it takes a bit more work to implement this functionality:

import re
quoted = re.compile(r'"[^"]*"')

class QuoteSaver(object):

  def __init__(self):
    self.saver = dict()
    self.reverser = dict()

  def preserve(self, mo):
    s = mo.group()
    if s not in self.saver:
      self.saver[s] = '"%d"' % len(self.saver)
      self.reverser[self.saver[s]] = s
    return self.saver[s]

  def expand(self, mo):
    return self.reverser[mo.group()]

x = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'

qs = QuoteSaver()
y = quoted.sub(qs.preserve, x)
kvs_strings = y.split(',')
kvs_pairs = [kv.split('=') for kv in kvs_strings]
kvs_restored = [(k, quoted.sub(qs.expand, v)) for k, v in kvs_pairs]

def converter(v):
  if v.startswith('"'): return v.strip('"')
  try: return int(v)
  except ValueError: return float(v)

thedict = dict((k.strip(), converter(v)) for k, v in kvs_restored)
for k in thedict:
  print "%-8s %s" % (k, thedict[k])
print thedict

I'm emitting thedict twice to show exactly how and why it differs from the required result; the output is:

age      34
location US
name     John Smith
avatar   :,=)
height   173.2
{'age': 34, 'location': 'US', 'name': 'John Smith', 'avatar': ':,=)',
 'height': 173.19999999999999}

As you see, the output for the floating point value is as requested when directly emitted with print, but it isn't and cannot be (since there IS no floating point value that would display 173.2 in such a case!-) when the print is applied to the whole dict (because that inevitably uses repr on the keys and values -- and the repr of 173.2 has that form, given the usual issues about how floating point values are stored in binary, not in decimal, etc, etc). You might define a dict subclass which overrides __str__ to specialcase floating-point values, I guess, if that's indeed a requirement.

But, I hope this distraction doesn't interfere with the core idea -- as long as the doublequotes are properly balanced (and there are no doublequotes-inside-doublequotes), this code does perform the required task of preserving "special characters" (commas and equal signs, in this case) from being taken in their normal sense when they're inside double quotes, even if the double quotes start inside a "field" rather than at the beginning of the field (csv only deals with the latter condition). Insert a few intermediate prints if the way the code works is not obvious -- first it changes all "double quoted fields" into a specially simple form ("0", "1" and so on), while separately recording what the actual contents corresponding to those simple forms are; at the end, the simple forms are changed back into the original contents. Double-quote stripping (for strings) and transformation of the unquoted strings into integers or floats is finally handled by the simple converter function.

like image 30
Alex Martelli Avatar answered Sep 29 '22 06:09

Alex Martelli


I would suggest a lazy way of doing this.

test_string = 'name="John Smith", age=34, height=173.2, location="US", avatar=":,=)"'
eval("dict({})".format(test_string))

{'age': 34, 'location': 'US', 'avatar': ':,=)', 'name': 'John Smith', 'height': 173.2}

Hope this helps someone !

like image 38
Chamath Avatar answered Sep 29 '22 06:09

Chamath