1

I am trying to use dict2xml to convert a nested dictionary to xml.

This code:

from dict2xml import dict2xml

data = {
  'a': 1,
  'b': [2, 3],
  'c': {
    'd': [
      {'p': 9},
      {'o': 10}
    ],
    'e': 7
  }
}

print dict2xml(data, wrap="all", indent="  ")  

Generated a correct xml like this:

<all>
  <a>1</a>
  <b>2</b>
  <b>3</b>
  <c>
    <d>
      <p>9</p>
    </d>
    <d>
      <o>10</o>
    </d>
    <e>7</e>
  </c>
</all>

However, if I change 'd' --> 'z', and maintains the order of keys by data = collections.OrderedDict(data), the order in the xml is incorrect and 'z' ends up after 'e' under 'c' in the xml, like this:

<all>
  <a>1</a>
  <b>2</b>
  <b>3</b>
  <c>
    <e>7</e>
    <z>
      <p>9</p>
    </z>
    <z>
      <o>10</o>
    </z>
  </c>
</all>

How can I run dict2xml without sorting the order of keys? Is there another solution to make a xml from my dict?

Thanks!

peter_parker
  • 133
  • 7

2 Answers2

0

Dicts inherently don't have key order before Python 3.7, so (to make my comment an answer) use a collections.OrderedDict if you need guaranteed key ordering.

AKX
  • 152,115
  • 15
  • 115
  • 172
  • It does not seem to work for dicts within dicts. When generating the dictionary called data, I tested to change 'd' to 'z', and then data= collections.OrderedDict(data). When executing dict2xml, 'e' is printed under 'c', followed by 'z'. – peter_parker Jun 07 '21 at 11:58
  • 1
    You'd need to also make any nested dicts, if any, OrderedDicts. – AKX Jun 07 '21 at 12:02
  • How do I do that for the dict data above? – peter_parker Jun 07 '21 at 12:21
0

As @AKX pointed out, you will need to apply collections.OrderedDict to nested dictionaries as well. You can use recursion to accomplish this:

from collections import OrderedDict
def to_od(d):
   if not isinstance(d, (dict, list)):
      return d
   if isinstance(d, list):
       return list(map(to_od, d))
   return OrderedDict({a:to_od(b) for a, b in d.items()})

data = {'a': 1, 'b': [2, 3], 'c': {'d': [{'p': 9}, {'o': 10}], 'e': 7}}
print(to_od(data))

Output:

OrderedDict([('a', 1), ('b', [2, 3]), ('c', OrderedDict([('d', [OrderedDict([('p', 9)]), OrderedDict([('o', 10)])]), ('e', 7)]))])

However, it is worth noting that converting your entire structure to a collections.OrderedDict in conjunction with dict2xml will take two traversals of the data. Using a custom dict-to-xml converter will only take one traversal:

def to_xml(d, indent="  "):
   def _to_xml(d, ind, p = None):
      if not isinstance(d, (dict, list)):
         yield f'{ind}<{p}>{d}</{p}>'
      elif isinstance(d, list):
         for i in d:
            yield from _to_xml(i, ind, p)
      else:
          p1, p2 = '' if p is None else f'{ind}<{p}>\n', '' if p is None else f'\n{ind}</{p}>'
          ind = ind if p is None else ind+indent
          for i in sorted(d): #sorting the keys
             if not isinstance(d[i], (dict, list)):
                yield f'{p1}{ind}<{i}>{d[i]}</{i}>{p2}'
             elif isinstance(d[i], dict):
                yield '{}{}<{}>\n{}\n{}</{}>{}'.format(p1, ind, i, '\n'.join(_to_xml(d[i], ind+indent)), ind, i, p2)
             else:
                yield from _to_xml(d[i], ind, p = i)
   return '<all>\n{}\n</all>'.format('\n'.join(_to_xml(d, indent)))

data = {'a': 1, 'b': [2, 3], 'c': {'d': [{'p': 9}, {'o': 10}], 'e': 7}}
print(to_xml(data))      

Output:

<all>
  <a>1</a>
  <b>2</b>
  <b>3</b>
  <c>
    <d>
      <p>9</p>
    </d>
    <d>
      <o>10</o>
    </d>
    <e>7</e>
  </c>
</all>
Ajax1234
  • 69,937
  • 8
  • 61
  • 102