Adding JSON Capability to SPServices

JSON Card -- Front

Image by superfluity via Flickr

For quite a while, I’ve gotten requests to offer some sort of JSON conversion capability in SPServices. The requests have often been fairly non-specific, meaning that they haven’t mentioned a particular Web Service operation, like GetListItems or GetWeb. However, I think that most people want to be able to get convert the XML they get back from GetListItems to JSON. This would be useful because a lot of the other jQuery plugins out there want JSON to work with.

I’ve written the beginnings of a function I will include in the next release of SPServices, and I wanted to offer it up for suggestions and requests. I want this converter function, which I’ve called SPXmlToJson, to be generally useful within an SPServices context. I don’t want to build a general-purpose converter; there are quite a few good ones out there. Generally, the data returned from SharePoint’s SOAP Web Services operations is fairly “flat”. By “flat”, I mean that there isn’t a huge amount of nesting. For example, GetListItems (which I expect to be the main context for using this function) simple passes back a z:row element for each item in the list with all of the column values as attributes.

Here’s an example:

<GetListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetListItemsResult>
    <listitems xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
      <rs:data ItemCount="22">
        <z:row ows_DocIcon="gif" ows_FileSizeDisplay="21530" ows_LinkFilename="cascaded_multiselect.GIF" ows_Title="3" ows_Modified="2011-07-05 12:17:54" ows_Editor="3;#Marc D Anderson" ows_Edit="0" ows_TestColumn="bar" ows__ModerationStatus="3" ows__Level="255" ows_ID="58" ows_owshiddenversion="5" ows_UniqueId="58;#{DF636FD7-2B27-4826-9E44-33E1F68F7EC4}" ows_FSObjType="58;#0" ows_Created_x0020_Date="58;#2011-04-13 00:35:51" ows_ProgId="58;#" ows_FileLeafRef="58;#cascaded_multiselect.GIF" ows_CheckoutUser="3;#Marc D Anderson" ows_FileRef="58;#Intranet/cascaded_multiselect.GIF" ows_MetaInfo="58;#vti_parserversion:SR|12.0.0.6421 vti_lmt:SW|Thu, 22 Oct 2009 03:42:52 GMT Order:DW|5800.00000000000 TestColumn:SW|bar vti_author:SR|SERVER\\username vti_lastwidth:IX|641 vti_winfileattribs:SW|00000000 vti_modifiedby:SR|SERVER\\username ContentTypeId:SW|0x01010077FE74FF93862D419170F4A09DD0BBC5 vti_ct:SW|Thu, 22 Oct 2009 03:42:52 GMT vti_lat:SW|Wed, 13 Apr 2011 04:34:58 GMT ContentType:SW|Document vti_title:SW|3 vti_sourcecontrolmultiuserchkoutby:VR|FSERVER\\\\username vti_lastheight:IX|286 " ows_Last_x0020_Modified="58;#2011-07-05 13:10:22"></z:row>
        <z:row ows_DocIcon="rtf" ows_FileSizeDisplay="1322" ows_LinkFilename="ChatLog.rtf" ows_Title="4" ows_Modified="2011-07-05 12:17:55" ows_Editor="3;#Marc D Anderson" ows_Edit="0" ows_TestColumn="bar" ows__ModerationStatus="3" ows__Level="255" ows_ID="59" ows_owshiddenversion="5" ows_UniqueId="59;#{7C8C1F98-E56C-4B8C-9BFB-50A4948BDC7D}" ows_FSObjType="59;#0" ows_Created_x0020_Date="59;#2011-04-13 00:35:52" ows_ProgId="59;#" ows_FileLeafRef="59;#ChatLog.rtf" ows_CheckoutUser="3;#Marc D Anderson" ows_FileRef="59;#Intranet/ChatLog.rtf" ows_MetaInfo="59;#vti_parserversion:SR|12.0.0.6421 vti_lmt:SW|Mon, 11 Apr 2011 17:09:08 GMT Order:DW|5900.00000000000 ContentTypeId:SW|0x01010077FE74FF93862D419170F4A09DD0BBC5 vti_ct:SW|Mon, 11 Apr 2011 17:09:08 GMT vti_lat:SW|Wed, 13 Apr 2011 04:34:59 GMT ContentType:SW|Document TestColumn:SW|bar vti_sourcecontrolmultiuserchkoutby:VR|SERVER\\\\username vti_title:SW|4 vti_author:SR|SERVER\\username vti_winfileattribs:SW|00000000 vti_modifiedby:SR|SERVER\\username " ows_Last_x0020_Modified="59;#2011-07-05 13:10:22"></z:row>
      </rs:data>
    </listitems>
  </GetListItemsResult>
</GetListItemsResponse>

Once you get past the enclosing “wrapper”, it’s very flat data: just a single element per item. Most of the other operations return similar structures (thought there’s precious little consistency). What I am thinking of is having the SPXmlToJson function accept a flat nodeset, which it will convert and return a JSON object. This ought to provide a very basic conversion capability that I can build on over time, as needed.

Here’s the function as it stands, and I’ve also made it available on the SPServices site in the latest alpha for v0.7.1. Please let me know what you think. Will this be enough? If not, what other options would you like to have available? What other operations are you likely to use SPXmlToJson with? Try it out and let me know your ideas.

Thanks for Paul Tavares and others for the help and nudging to get me to this point.

// This function converts a nodeset to JSON
$.fn.SPServices.SPXmlToJson = function(options) {

  var opt = $.extend({}, {
  ns: ""    // A flat XML nodeset (as from GetListItems)
  }, options);

  var json = [];

  opt.ns.each(function() {
  var row = {};
  var rowAttrs = this.attributes;
  for (var e=0; e < rowAttrs.length; e++) {
    var thisNodeName = rowAttrs[e].name;
    var trimmedNodeName = thisNodeName.indexOf("ows_") !== -1 ? thisNodeName.substring(4) : thisNodeName;
    row[trimmedNodeName] = rowAttrs[e].value;
  }
  json.push(row);
  });
  return json;
}; // End $.fn.SPServices.SPXmlToJson
Enhanced by Zemanta

14 Comments

    • Christophe:

      I’m not sure that there would be a benefit to that. I expect that I’ll be adding other options as well, so passing everything in as an option seems to make more sense to me. Also, for GetListItems, using your approach would look like this:

      $($(xData.xmlResponse).SPFilterNode("z:row")).SPXmlToJson();
      

      That seems like a messier approach, though you could, of course, create a variable first. Here’s what my way would look like:

      $().SPServices.SPXmlToJson({
        ns: $(xData.xmlResponse).SPFilterNode("z:row")
      });
      

      M.

      Reply
  1. Marc,
    Great work on adding this new utility… I do feel it will be useful as more and more browser client side frameworks move away from XML (as seen with SP2010, with its new REST API).
    Had I had this option back when I started to work with Sharepoint and SPServices, I probably would not have had to learn XSLT in order to present XML (although I don’t regret learning it).

    I like @Christophe sugestion, although you would problem have to create it as $(ns).SPXmlToJson() (a jquery pugin).

    I have only one suggestion… Your code above assumes the input option ‘ns’ is already a jquery object… I suggest wrapping it in jquery just to be safe and insure you always get a jquery object to work with ($(opt.ns).each(...)

    Should performance be slow (with large quantities of items (ex. 1500 rows), I suggest you move way from using jQuery’s .each() and instead use javascript native loops (for (...){...})… also, with large arrays and looping, stay away from using .length in a loop condition… much faster if you store the total number of elements prior to the loop, and then just use that.

    Paul.

    (in a prior job, I assessed applications for performance improvements :) )

    Reply
    • Paul, you’re absolutely right, and I wrote too fast in my first comment. It should be a jQuery function to take advantage of chainability, as I wrote in the second comment.
      Completely agree with moving length out of the loop too. But I don’t undderstand the benefit of replacing each with native js.

      Reply
      • Re: not using jquery’s .each()

        When you benchmark jquery’s .each() vs a for(){} loop, the loop is faster at parsing the array… you don’t incur the overhead of the .each function. Perhaps not a big deal.

        Paul.

        Reply
  2. Marc,

    This will be a very handy addition. I would also like to see you support Christophe’s suggestion to better support chaining. It feels more natural to me.

    Thanks much for your continued support of the SharePoint community.

    Reply
  3. I have a SharePoint 2007 List call FAQ… how can I use this function to turn the list into a JSON return and incorporate it to an Auto complete function that is currently parsing a static JSON file.

    Reply

Leave a Reply