Parsing Suds SOAP complex data type into Python dict
Asked Answered
L

6

8

I have some data coming from a SOAP API using Suds which I need to parse in my Python script. Before I go off and write a parser (there is more than just this one to do):

1) Does anyone recognise what this is? It's the standard complex object datatype as returned by Suds (documentation). Should have spotted that.

2) If so, is there an existing library that I can use to convert it to a Python dictionary? How do I parse this object into a Python dict? It seems I can pass a dictionary to Suds but can't see an easy way of getting one back out.

(ArrayOfBalance){
   Balance[] = 
      (Balance){
         Amount = 0.0
         Currency = "EUR"
      },
      (Balance){
         Amount = 0.0
         Currency = "USD"
      },
      (Balance){
         Amount = 0.0
         Currency = "GBP"
      },
 }
Lynnet answered 10/7, 2013 at 22:3 Comment(4)
wow .. I can honestly say that in 20 years of doing this, I've never seen that form of serialization. Now I HAVE to know what that is lol. Can you give us a context for the data?Chops
Glad it's not just me! It's the wallet balance response from OKPay's APILynnet
It seems like some self-made data structure :-\Systematic
As I suspected. It actually behaves like an object though. If you try and response.split(',') it gives AttributeError: ArrayOfBalance instance has no attribute 'split'. Gonna dig into the docs a bit more.Lynnet
W
21

There is a class method called dict in suds.client.Client class which takes a sudsobject as input and returns a Python dict as output. Check it out here: Official Suds Documentation

The resulting snippet becomes as elegant as this:

from suds.client import Client

# Code to obtain your suds_object here...

required_dict = Client.dict(suds_object)

You might also want to check out items class method (link) in the same class which extracts items from suds_object similar to items method on dict.

Woodall answered 10/10, 2016 at 21:11 Comment(3)
now that i got it to a python dic how i can extract the information form it.Squirm
gives you a shallow copy which is worthless if you have nested sudsobjectsHardwick
this worked for some of my requirements - thank you :)Jakejakes
B
9

You can cast the object to dict(), but you still get the complex data type used by suds. So here are some helpful functions that I wrote just for the occasion:

def basic_sobject_to_dict(obj):
    """Converts suds object to dict very quickly.
    Does not serialize date time or normalize key case.
    :param obj: suds object
    :return: dict object
    """
    if not hasattr(obj, '__keylist__'):
        return obj
    data = {}
    fields = obj.__keylist__
    for field in fields:
        val = getattr(obj, field)
        if isinstance(val, list):
            data[field] = []
            for item in val:
                data[field].append(basic_sobject_to_dict(item))
        else:
            data[field] = basic_sobject_to_dict(val)
    return data


def sobject_to_dict(obj, key_to_lower=False, json_serialize=False):
    """
    Converts a suds object to a dict.
    :param json_serialize: If set, changes date and time types to iso string.
    :param key_to_lower: If set, changes index key name to lower case.
    :param obj: suds object
    :return: dict object
    """
    import datetime

    if not hasattr(obj, '__keylist__'):
        if json_serialize and isinstance(obj, (datetime.datetime, datetime.time, datetime.date)):
            return obj.isoformat()
        else:
            return obj
    data = {}
    fields = obj.__keylist__
    for field in fields:
        val = getattr(obj, field)
        if key_to_lower:
            field = field.lower()
        if isinstance(val, list):
            data[field] = []
            for item in val:
                data[field].append(sobject_to_dict(item, json_serialize=json_serialize))
        elif isinstance(val, (datetime.datetime, datetime.time, datetime.date)):
            data[field] = val.isoformat()
        else:
            data[field] = sobject_to_dict(val, json_serialize=json_serialize)
    return data


def sobject_to_json(obj, key_to_lower=False):
    """
    Converts a suds object to json.
    :param obj: suds object
    :param key_to_lower: If set, changes index key name to lower case.
    :return: json object
    """
    import json
    data = sobject_to_dict(obj, key_to_lower=key_to_lower, json_serialize=True)
    return json.dumps(data)

If there is an easier way, I would love to hear about it.

Bloater answered 18/1, 2016 at 4:7 Comment(0)
L
4

The right answer, as is often the case, is in the docs. The bits in (brackets) are objects which can contain other objects or types.

In this case we have an ArrayOfBalance object which contains a list of Balance types, each of which has the attributes of Amount and Currency.

These can all be referred to using . notation so the following one-liner does the trick.

balance = {item.Currency: item.Amount for item in response.Balance}  
Lynnet answered 11/7, 2013 at 0:2 Comment(2)
The link you provided is dead -- has it been rehosted somewhere else?Pul
Bear in mind this was 5 years ago, and fedora hosting doesn't seem to exist any more. I've edited in a rescued copy of the docs, but the important parts are in the answer here.Lynnet
S
4

Found one solution:

from suds.sudsobject import asdict

def recursive_asdict(d):
    """Convert Suds object into serializable format."""
    out = {}
    for k, v in asdict(d).iteritems():
        if hasattr(v, '__keylist__'):
            out[k] = recursive_asdict(v)
        elif isinstance(v, list):
            out[k] = []
            for item in v:
                if hasattr(item, '__keylist__'):
                    out[k].append(recursive_asdict(item))
                else:
                    out[k].append(item)
        else:
            out[k] = v
    return out

def suds_to_json(data):
    return json.dumps(recursive_asdict(data))

If subs are just nested dict and list, it should work.

Solanum answered 6/5, 2015 at 1:54 Comment(0)
F
3

I was encountering a similar problem and had to read a suds response. Suds response will be returned as a tuple consisting of objects. len(response) will show you the number of objects contained in the suds response tuple. In order to access the first object we need to give response[0].

In IDLE if you type >>>print response[0] followed by a period '.' symbol you will get a popup showing the various objects that can be accessed from this level.

Example: if you type response[0]. it will bring a popup and show the Balance object so the command would now become response[0].Balance.

You can follow the same approach to get the list of objects under the subsequent levels

Fluency answered 25/12, 2014 at 21:12 Comment(0)
E
0

The checkaayush's answer is not recursive so, it does not consider the nested objects.

Based on aGuegu Answer i did some changes to solve an issue when the suds object has dicts inside lists.

It works!


from suds.sudsobject import asdict

def recursive_asdict(d):
    """Convert Suds object into serializable format."""
    out = {}
    for k, v in asdict(d).items():
        if hasattr(v, '__keylist__'):
            out[k] = recursive_asdict(v)
        elif isinstance(v, list):
            out[k] = []
            for item in v:
                if hasattr(item, '__keylist__'):
                    out[k].append(recursive_asdict(item))
                elif not isinstance(item, list):
                    out[k] = item
                else:
                    out[k].append(item)
        else:
            out[k] = v
    return out
Eugenol answered 22/2, 2018 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.