locked
Multiple Element Name RRS feed

  • Question

  • User-1245426334 posted

    Hi, everyone!

    I'm trying to create classes as needed to deserialize XML being returned through an API call. As part of this process, I've reached a point where I need multiple node options to map to a single property (the XML occasionally changes the node name).

    So I have this:

    [XmlArray("groups")]
    [XmlArrayItem("group")]
    public List<T> items { get; set; }

    But I need groups and group to also be items and item.

    I thought I read somewhere that you can assign multiple attributes (kind of like a switch statement) to a single property, but I can't find any real examples of this to know how to try it out.

    Could someone help?

    Thanks!

    -Eric

    Wednesday, September 23, 2015 2:51 PM

All replies

  • User-484054684 posted

    Hi Eric,

    The feasible and easy way to consume the third party API call - is to - generate the cs class files from the XSD/WSDL and use some kind of mapping logic at your server side to map the generated objects back to your domain model objects.

    You have the XSD, WSDL - if you are using webservices/basicHttpBinding of WCF. You also have svcUtil tool for other WCF bindings.

    As you are using XML attributes - probably XSD and WSDL tools are more useful.

    If you have other reasons to go with custom mappings instead of using these tools, that is fine. Otherwise, I would suggest to go with the in-built tools to make it more easier.

    See this example for XSD.exe tool usage:

    If you just want classes:

    xsd fileName.xsd /classes

    Reference: http://www.overpie.com/aspnet/articles/generate-class-file-from-xsd-using-visual-studio-command-prompt - A good step by step article!

    MSDN Reference of the tool usage: https://msdn.microsoft.com/en-us/library/x6c1kb0s%28v=vs.110%29.aspx

    The following command creates a client proxy class in the C# language for an XML Web service located at the specified URL. The tool saves the client proxy class in the file <tt>myProxyClass.cs</tt>.

    wsdl /out:myProxyClass.cs http://hostServer/WebserviceRoot/WebServiceName.asmx?WSDL
    

    Reference: https://msdn.microsoft.com/en-us/library/7h3ystb6%28VS.80%29.aspx

    Wednesday, September 23, 2015 3:57 PM
  • User-1245426334 posted

    Thanks, Siva.

    I've been using those tools to help create the class structure and they're great for generating basic structures for each call, but I'm trying to also create an abstraction of all API calls so my code is more robust and useable.

    Let me give you an example. One API call returns this:

    <?xml version="1.0" encoding="UTF-8"?>
    <ccb_api>
      <request>
        <parameters>
          <argument value="public_calendar_listing" name="srv"/>
          <argument value="2008-11-25" name="date_start"/>
        </parameters>
      </request>
      <response>
        <items count="1">
          <item>
            <date>2008-11-25</date>
            <event_name ccb_id="586">a really fun event</event_name>
            <event_description></event_description>
            <start_time>00:00:00</start_time>
            <end_time>00:15:00</end_time>
            <event_duration>15</event_duration>
            <event_type>Registration Required</event_type>
            <location></location>
            <group_name ccb_id="8">Awesome Group For Fun People</group_name>
            <group_type>Life Group</group_type>
            <grouping_name>FUN GROUPING</grouping_name>
            <leader_name ccb_id="841">Larry Boy</leader_name>
            <leader_phone>(123) 938-5555</leader_phone>
            <leader_email>lboy@test.com</leader_email>
          </item>
        </items>
      </response>
    </ccb_api>

    And a second returns this:

    <?xml version="1.0" encoding="UTF-8"?>
    <ccb_api>
      <request>
        <parameters>
          <argument value="group_profile_from_id" name="srv"/>
          <argument value="23" name="id"/>
        </parameters>
      </request>
      <response>
        <service>group_profile_from_id</service>
        <service_action>execute</service_action>
        <availability>public</availability>
        <groups count="1">
          <group id="23">
            <name>**Ninja Turtle Club**</name>
            <description>Fight by night</description>
            <image>https://s3.amazonaws.com/ccbchurch/40622/pics/g_23?AWSAccessKeyId=AKIAJ4CISARDRJPE4ERQ&amp;Expires=1390930062&amp;  Signature=pzP73TsoROOhQhZyEu2Deg3Tsy4%3D</image>
            <campus id="1">Church of Cucumbers</campus>
            <main_leader id="49">
              <first_name>Group</first_name>
              <last_name>Leader</last_name>
              <full_name>Group Leader</full_name>
              <email>tsebastian@churchcommunitybuilder.com</email>
              <phones>
                <phone type="contact"></phone>
              </phones>
            </main_leader>
            <leaders>
              <leader id="34">
                <first_name>Assistant</first_name>
                <last_name>Group Leader</last_name>
                <full_name>Assistant Group Leader</full_name>
                <email>tsebastian@ccbhq.com</email>
                <phones>
                  <phone type="contact"></phone>
                </phones>
              </leader>
            </leaders>
            <participants/>
            <group_type id="1">Care / Small Group</group_type>
            <department id="0"></department>
            <area id="0"></area>
            <calendar_feed>webcal://yourchurch.ccbchurch.com/group_calendar.ics?id=23&amp;tk=9AABQK9DBD4ZUK426MSM6QZSKDJKS2U9</calendar_feed>
            <registration_forms/>
            <current_members>9</current_members>
            <group_capacity>Unlimited</group_capacity>
            <addresses/>
            <meeting_day id="0"></meeting_day>
            <meeting_time id="0"></meeting_time>
            <childcare_provided>false</childcare_provided>
            <interaction_type>Members Interact</interaction_type>
            <membership_type>Invitation or Request Required</membership_type>
            <notification>false</notification>
            <user_defined_fields/>
            <listed>true</listed>
            <public_search_listed>false</public_search_listed>
            <inactive>false</inactive>
            <creator id="1">Larry Cucumber</creator>
            <modifier id="1">Larry Cucumber</modifier>
            <created>2012-09-20 08:51:01</created>
            <modified>2012-10-05 16:34:47</modified>
          </group>
        </groups>
      </response>
    </ccb_api>

    So essentially, the containers for both of these sets of data are the same: Request and Response. Response will contain specific types based on the API call, so I created a templated class:

    [XmlRootAttribute("response")]
    public class Response<T>
    {
        [XmlArray("groups")]
        [XmlArrayItem("group")]
        public List<T> items { get; set; }
    }

    The problem I'm facing is that the values I'm using for the XmlArray attribute and XmlArrayItem attribute would change based on each API response. For the first response, it needs to be "items" and "item." For the second it needs to be as shows "groups" and "group."

    I swear I saw that this was possible somewhere because all I'm telling the Deserialize method is what element gets applied to what property, in this case an array of items.

    So my question is: how do I do that? How do I specify [XmlArray("groups" OR "items")] and [XmlArrayItem("group" OR "item"].

    Seems like this would be pretty straight forward, but I haven't been able to figure it out.

    Does that make more sense?

    Thanks!

    -Eric

    Wednesday, September 23, 2015 4:07 PM
  • User-484054684 posted

    Hi Eric,

    There is one more query - Your response object not only contain the Array but it also contains other attributes, so how would generating Generic response call be much useful, when you may have lot of Response classes and you might end up updating each Response class in the generated class manually?

    Though I saw that the XmlArray and XmlArrayItem attributes which are used for one-to-one for a class. Your implementation is quite interesting to try to get it dynamic.

    During a bit of research, I found few other articles, which may give a few ideas:

    1. See: http://stackoverflow.com/a/14095798 - Implementing generic <T> response for List<T>. However, note: <Content> to seems to be same across all responses in their case. (i.e, only array item element is dynamic not the array element)

    2. Usage of multiple [XmlArrayItem] attributes as in: http://stackoverflow.com/a/2261259 - This may also work to handle XmlArrayItem attributes but the XmlArray is single option. See also: http://stackoverflow.com/a/3512088 .

    BUT, this required to declare [XmlInclude] as well on the base type as in: http://stackoverflow.com/a/15919090 (Probably only for arrays?) - I didn't completely check it.

    You may get more ideas after looking at these examples. But again, I'd not be quite comfortable in manually amending the generated classes unless there is high need - as maintaining these modifications will cause the problems as these need to be taken care every time when the API updates it's signature.

    Wednesday, September 23, 2015 4:41 PM
  • User-1245426334 posted

    Thanks, Siva.

    Maybe I'm making this all too complicated - I've been known to do that in the past. My thought was to try and abstract out the methods for making an API call as well as deserializing the data. That may be where I'm wrong.

    I could still abstract out the MakeRequest method to get the XML response, but then each response type class can have it's own deserialize method.

    That just seemed to create unnecessary redundancy, in my opinion. Plus, if I needed to change the way things were being deserialized, I'd have to change the method in every single class.

    I hope my explanation makes sense. My ultimate goal is to have the most robust and clean code I can, so I appreciate your wisdom and advice here.

    Thanks!

    -Eric

    Thursday, September 24, 2015 8:58 AM