user636322
user636322

Reputation: 1201

Python for loop over json data throws 'TypeError: string indices must be integers' only when a single element of data

I've inherited the following code which is working great, apart from when only a single data item is return from the original xml. When that occurs the following error is thrown: 'TypeError: string indices must be integers'

 result = xmltodict.parse(get_xml())

 latest_result = result['Response']['Items']['Item']
 myJsonData = json.dumps(latest_result)

 j=  json.loads(myJason)
 print type(j)

 for item in j:
    print (item['Id'])
    print (item['OrderId'])

I have narrowed the change in behaviour to a difference in datatype here:

print type(j)

When only a single ['Item'] is returned from the source XML the datatype of j is a 'dict', whilst the rest of the time (greater than one ['Item']) its a 'list'.

Hope someone can help.

Upvotes: 0

Views: 518

Answers (2)

alecxe
alecxe

Reputation: 473873

This is a common xmltodict module usage problem. When there is a single child, by default, it makes a dict out of it and not a list with a single item. Relevant github issue:

To workaround it, one option would be to set the dict_constructor argument:

from collections import defaultdict

xmltodict.parse(xml, dict_constructor=lambda *args, **kwargs: defaultdict(list, *args, **kwargs))

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1122282

Encoding to JSON then decoding again has nothing to do with your question. It is a red herring, you can use latest_result and still get the same error.

The result['Response']['Items']['Item'] can evidently be either a list of dictionaries, or a single dictionary. When iterating over a list, you'll get contained elements, while iteration over a dictionary gives you the keys. So your item elements are strings (each a key in the dictionary) and you can't address elements in that string with 'Id' or 'OrderId'.

Test for the type or use exception handling:

if isinstance(latest_result, dict):
    # just one result, wrap it in a single-element list
    latest_result = [latest_result]

Alternatively, fix up the xmltodict code (which you didn't share or otherwise identify) to always return lists for elements, even when there is just a single one.

Upvotes: 1

Related Questions