Blog An exploration of the art and
craft of software development

Mapping Soap Response without wsdl

Posted by Marty Haught on Friday, August 04, 2006
So I've been wrestling with trying to map a Soap4R response to Ruby classes. While I got really close I couldn't quite get this Account object to map an array of attributes (the a element below). Here's a snippet of the xml response:

       <CreateAccountResponse xmlns="urn:zimbraAdmin">
            <account name="fri1@dev.razorstream.com" id="19832977-01ae-4290-a753-bc78c82196da">
                <a n="zimbraPrefMailPollingInterval">5m</a>
                <a n="zimbraContactMaxNumEntries">0</a>
                <a n="zimbraPrefGroupMailBy">conversation</a>
 <snipped a ton of additional 'a' elements>
            </account>
        </CreateAccountResponse>
Though I suspect there are plenty more inner workings of Soap4R that would make this easier I've come to the easiest solution for accessing this data. Instead of trying to map a hand-coded class I just let it come back as a soap mapping object. This works for most things but in this case the array of 'a' elements are all strings. The strings are just the value of the 'a' element tag. The vital piece, the 'n' attribute, seems to be missing... or hidden. It seems that Soap4R modifies the value's String class to have an __xmlattr attribute. Look at the following yaml output of the account object:

!ruby/object:Account
__xmlattr:
  !ruby/object:XSD::QName ?
    name: id
    namespace:
    source:
  : 19832977-01ae-4290-a753-bc78c82196da

  !ruby/object:XSD::QName ?
    name: name
    namespace:
    source:
  : wed7@zdev1.razorstream.com

a:
- !str
  str: 5m
  \"@__xmlattr\":
    !ruby/object:XSD::QName ?
      name: n
      namespace:
      source:
    : zimbraPrefMailPollingInterval
Curious, but if you do an inspect you won't see these hidden nuggets. In this particular case I was able to write this converter method to pull these out and make them easier to get to:

      if response.account
        acct = response.account
        class << acct
          attr_accessor :config, :id, :name   
        end    
        # Iterate over user properties/config and add to the config hash
        acct.config = Hash[*acct.a.map do |value|
          [value.__xmlattr[XSD::QName.new(nil, 'n')], String.new(value)]
        end.flatten]
       
        # Populate our id and name attributes
        acct.id = acct.__xmlattr[XSD::QName.new(nil,"id")]
        acct.name = acct.__xmlattr[XSD::QName.new(nil,"name")]
        @account = acct
      end
I'd still prefer to map these objects correctly so that I don't need to run code like this. However, this is an acceptible solution for now.
blog comments powered by Disqus