9

I'm using xmltodict to parse an XML config. The XML has structures where an element can occur in 1 to n instances, where both are valid:

<items>
    <item-ref>abc</item-ref>
</items>

and

<items>
    <item-ref>abc</item-ref>
    <item-ref>dca</item-ref>
    <item-ref>abb</item-ref>
</items>

I'm parsing this with xmltodict as follows:

document['items']['item-ref']

and it gives back a single unicode or a list (depending the items found), so I always need to add an extra check to ensure if I need to handle a list or a string:

if isinstance(document['items']['item-ref'], list):
    my_var = document['items']['item-ref']
else:
    my_var = [document['items']['item-ref']] #create list manually

Is there a better/simpler/more elegant way to handle these?

matsjoyce
  • 5,744
  • 6
  • 31
  • 38
balas
  • 216
  • 3
  • 11
  • 1
    ran into same issue, and have checks through everything :) – dm03514 Dec 15 '14 at 15:12
  • If you want to get your hands a teensy bit dirty, you can patch `xmltodict.py`. There is a method, `_DictSAXHandler.push_data` which determines which elements to turn into lists. You can alter the arguments so that you can pass the parser a set of elements to *always* turn into a list. – Joel Cornett Dec 15 '14 at 18:40

1 Answers1

4

I am not sure of a super elegant way to do this. Python doesn't have any built-in methods or functions that will help you do this in a single line of code. Nevertheless, in the interest of consolidating code, you will want to do something. As matsjoyce mentioned in a comment, you may simply want to create a function that will take care of this logic for you.

def listn(val=None):
    if val is None:
        return []
    return val if isinstance(val, list) else [val]

>>> listn([1, 2, 3])
[1, 2, 3]
>>> listn()
[]
>>> listn(3)
[3]

Now you can simply call that function with your val and expect a list to be returned, whether it is a list, an int, or a NoneType object:

my_var = listn(document['items']['item-ref'])

(My first answer created a subclass of list, but that takes 6 lines instead of 4 and changes the type--not the most ideal side-effect.)

Justin O Barber
  • 11,291
  • 2
  • 40
  • 45
  • 1
    Wouldn't that be nicer just as a function? You can do it in a one-liner: `listify=lambda x: x if isinstance(x, list) else [x]` – matsjoyce Dec 15 '14 at 17:13
  • Fair enough. I'm not sure I would use the one-liner, but a function would certainly be easier, especially if it could also accommodate `None` values. I updated my answer above. – Justin O Barber Dec 15 '14 at 18:15
  • This is a good approach making my code become more simple and compact. – balas Dec 15 '14 at 23:55
  • @balas Glad it works for you. Great question! I wish there was a more native way to do this. – Justin O Barber Dec 16 '14 at 01:18