lu4nx
lu4nx

Reputation: 94

How to convert a malformed string to a dictionary?

I have a string s (note that the a and b are not enclosed in quotation marks, so it can't directly be evaluated as a dict):

s = '{a:1,b:2}'

I want convert this variable to a dict like this:

{'a':1,'b':2}

How can I do this?

Upvotes: 3

Views: 4262

Answers (5)

root
root

Reputation: 80426

import re,ast
regex = re.compile('([a-z])')
ast.literal_eval(regex.sub(r'"\1"', s))

out:

{'a': 1, 'b': 2}

EDIT: If you happen to have something like {foo1:1,bar:2} add an additional capture group to the regex:

regex = re.compile('(\w+)(:)')
ast.literal_eval(regex.sub(r'"\1"\2', s))

Upvotes: 2

mgilson
mgilson

Reputation: 310167

This will work with your example:

import ast
def elem_splitter(s):
    return s.split(':',1)

s = '{a:1,b:2}'
s_no_braces = s.strip()[1:-1] #s.translate(None,'{}') is more elegant, but can fail if you can have strings with '{' or '}' enclosed.
elements = (elem_splitter(ss) for ss in s_no_braces.split(','))
d = dict((k,ast.literal_eval(v)) for k,v in elements)

Note that this will fail if you have a string formatted as:

'{s:"foo,bar",ss:2}'  #comma in string is a problem for this algorithm

or:

'{s,ss:1,v:2}' 

but it will pass a string like:

'{s ss:1,v:2}' #{"s ss":1, "v":2}

You may also want to modify elem_splitter slightly, depending on your needs:

def elem_splitter(s):
    k,v = s.split(':',1)
    return k.strip(),v # maybe `v.strip() also?`

*Somebody else might cook up a better example using more of the ast module, but I don't know it's internals very well, so I doubt I'll have time to make that answer.

Upvotes: 5

Cartroo
Cartroo

Reputation: 4343

You can do it simply with this:

s = "{a:1,b:2}"
content = s[s.index("{")+1:s.index("}")]
to_int = lambda x: int(x) if x.isdigit() else x
d = dict((to_int(i) for i in pair.split(":", 1)) for pair in content.split(","))

For simplicity I've omitted exception handling if the string doesn't contain a valid specification, and also this version doesn't strip whitespace, which you may want. If the interpretation you prefer is that the key is always a string and the value is always an int, then it's even easier:

s = "{a:1,b:2}"
content = s[s.index("{")+1:s.index("}")]
d = dict((int(pair[0]), pair[1].strip()) for pair in content.split(","))

As a bonus, this version also strips whitespace from the key to show how simple it is.

Upvotes: 0

Abhijit
Abhijit

Reputation: 63777

As your string is malformed as both json and Python dict so you neither can use json.loads not ast.literal_eval to directly convert the data.

In this particular case, you would have to manually translate it to a Python dictionary by having knowledge of the input data

>>> foo = '{a:1,b:2}'
>>> dict(e.split(":") for e in foo.translate(None,"{}").split(","))
{'a': '1', 'b': '2'}

As Updated by Tim, and my short-sightedness I missed the fact that the values should be integer, here is an alternate implementation

>>> {k: int(v) for e in foo.translate(None,"{}").split(",") 
     for k, v in [e.split(":")]}
{'a': 1, 'b': 2}

Upvotes: 2

Jun HU
Jun HU

Reputation: 3314

import simplejson
s = '{a:1,b:2}'
a = simplejson.loads(s)
print a

Upvotes: -2

Related Questions