Archive

Posts Tagged ‘JSON’

jQuery File Upload: IE9 and ASP.Net Web API File Uploading

June 2nd, 2016 Admin No comments

    The blueimp jQuery File Upload plugin uses the XMLHttpRequest to pass the file data to a server (only IE10+). If browser doesn’t support Ajax file uploading, the plugin makes a workaround by dynamically creating IFrame and sending the data on behalf of it through the traditional form POST. The JavaScript responsible for the workaround resides in jquery.iframe-transport.js, which accompanies the basic jquery.fileupload.js. The following TypeScript code could be used to initialize the plugin and submit file data (the code is intentionally kept as simple as possible – no progress bars, validations and so on):

//...
private fileData: any = {};
//...
$(".filePicker").fileupload({
	autoUpload: false, // will be submitted once button is clicked
	method: "PUT",
	dataType: "json",
	url: "api/someController/someMethod", // Web Api method to receive and process the file
	formData: () => { // additional parameters accompanying the file data
		return [{
			name: "bookName",
			value: $("#bookName").val()
		},
		{
			name: "bookGenre",
			value: $("#bookGenre").val()
		},
		{
			name: "bookAuthor",
			value: $("#bookAuthor").val()
		}];
	},
	add: (e: JQueryEventObject, data: any) => { // event handlers
		this.fileData = data;
		//...
	},
	done: (e: JQueryEventObject, data: any) => {
		//...
	},
	fail: (e: JQueryEventObject, data: any) => {
		//...
	},
	always: (e: JQueryEventObject, data: any) => {
		//...
	}
});
//...
$("#loadBook").on('click', function () { // the button to initiate the file sending
	if (this.fileData && this.fileData.hasOwnProperty("process")) {

		// file data validation: size, extension, whatever else...

		this.fileData.process().done(() => { // file sending
			this.fileData.submit();
		});
	}
});

On the server side the following code receives and processes the file data:

using System;
using System.Text;
using System·Web;
using System·Web.Http;
using System.Net;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Linq;
...
namespace DotNetFollower.Web.Controllers.Api
{
    [RoutePrefix("api/someController")]
	public class someController : ApiController
	{
        public someController()
		{
            //...
		}

        [Route("someMethod")]
        [HttpPut]
        [HttpPost] // this attribute allows processing traditional form POST
        public ServiceResult<BookOutput> someMethod()
        {
            try
            {
			    //...
			    var request = HttpContext.Current.Request;
			    var files   = request.Files;

			    if (files.Count == 0)
				    throw new Exception("Couldn't find a book to load!");

			    // read accompanying parameters
			    var bookName   = request.Form.Get("bookName");
			    var bookGenre  = request.Form.Get("bookGenre");
			    var bookAuthor = request.Form.Get("bookAuthor");

			    var file = new HttpPostedFileWrapper(files[0]);

			    // parsing file.InputStream ...

			    // processing the parsed file data ...

			    file.InputStream.Close();
			    //...

			    return new ServiceResult<BookOutput>()
				    {
					    Data = new BookOutput()
						    {
							    Name   = bookName,
							    Genre  = bookGenre,
							    Author = bookAuthor
						    }
				    };
            }
            catch(Exception ex)
            {
                return new ServiceResult<BookOutput>(ex);
            }
        }
	}
}

// Where ServiceResult and BookOutput are defined as follows

public class ServiceResult<T>
{
	public bool   Success      { get; set; }
	public string ErrorMessage { get; set; }
	public T      Data         { get; set; }

	public ServiceResult()
	{
		Success = true;
	}

	public ServiceResult(string errorMessage)
	{
		ErrorMessage = errorMessage;
	}

	public ServiceResult(Exception exception)
	{
		ErrorMessage = exception.Message;
	}
}

public class BookOutput
{
	public string Name   { get; set; }
	public string Genre  { get; set; }
	public string Author { get; set; }
}

Unfortunately, the code doesn’t works as expected in Internet Explorer 9 (thankfully, the lower versions are not supposed to be supported by the project, so I don’t care about them). If IE9 sends an Ajax request to the Web Api method, it interprets the JSON response correctly. However, when uploading a file, the jQuery File Upload plugin sends traditional non-Ajax form POST. So, having received the JSON result, IE9 prompts for a JSON file download.

Download Json File Prompt

To bypass such IE9 behaviour the server response should contain the content-type header “text/html” rather than the “application/json” returned by the Web Api method by default. To avoid writing some IE9 specific logic on both server and client sides, I’ve introduced the following Web Api method-adapter:

[Route("someMethodAdapted")]
[HttpPut]
[HttpPost] // this attribute allows processing traditional form POST
public HttpResponseMessage someMethodAdapted()
{
	var res = someMethod(); // call the original method

	const string JsonContentType = "application/json";
	const string HtmlContentType = "text/html";

	var response = Request.CreateResponse(HttpStatusCode.OK); // return 200 OK
	var context  = HttpContext.Current;
	response.Content = new StringContent(ToJson(res), // serialize result object into JSON
	  Encoding.UTF8,
	  // check if the JSON content-type is accepted
	  // (it's not accepted in case of form POST coming from IE9)
	  context.Request.AcceptTypes.Contains(JsonContentType, StringComparer.OrdinalIgnoreCase) ?
	     JsonContentType : HtmlContentType); // return suitable content-type

	return response;
}

// the ToJson method is defined as follows

private static string ToJson<T>(T obj) where T : class
{
	if (obj == null)
		return string.Empty;

	DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
	using (MemoryStream stream = new MemoryStream())
	{
		serializer.WriteObject(stream, obj);
		return Encoding.Default.GetString(stream.ToArray());
	}
}

The someMethodAdapted is supposed to be used instead of someMethod everywhere on the client side. So, repoint the url to the method-adapter

...
$(".filePicker").fileupload({
	...
	// Web Api method-adapter to receive and process the file
	url: "api/someController/someMethodAdapted",
	...
});
...

The someMethodAdapted makes a content-type trick and perfectly serves IE9 and higher. The use of HttpResponseMessage gives a control over the response headers. The HttpContext.Current.Request.AcceptTypes is a list of client-supported content types (aka MIME types). If the “application/json” is not in the list, the “text/html” is the right choice. Below are the AcceptTypes of Ajax and non-Ajax requests made by IE9:

// IE9 Ajax request to a Web Api method (true for higher browser versions too)
HttpContext.Current.Request.AcceptTypes	{string[3]}	string[]
[0]	"application/json"	string
[1]	"text/javascript"	string
[2]	"*/*; q=0.01"	string

// IE9 non-Ajax form POST
HttpContext.Current.Request.AcceptTypes	{string[3]}	string[]
[0]	"text/html"	string
[1]	"application/xhtml+xml"	string
[2]	"*/*"	string

WebLoading Library: Downloading data from RESTful sources

July 23rd, 2012 Admin No comments

    I often run into necessity to load data from sources published in the Internet. In most cases the sources are RESTful web-services, thus the data they emit is in xml or json formats. More and more web-services providers prefer spreading their data in an easier-to-use REST formats as a simpler alternative to SOAP and WSDL based web-services.


WebLoading Library Overview

For downloading data via the Http protocol I implemented a set of generic classes, let’s call it WebLoading Library. Almost every class in the library is a loader, which operates at a certain level of abstraction. The library allows to easily derive a new class from one of the exposed loaders, defining a type of the data to be loaded and the type of the ready-to-use data to be returned out of the loader. See the class diagram below.

Class Diagram of WebLoading Library

I single out such basic steps of getting data as raw data loading, verification and conversion into a ready-to-use view. All loaders exposes the LoadData method aimed at performing these steps.The listing below demonstrates the LoadData‘s base implementation in the BaseLoader class. BaseLoader is a root base class for all loaders in the WebLoading Library hierarchy.

BaseLoader Source

using System;

namespace WebLoading
{
    /// <summary>
    /// Abstracts the data loading from a specified source. Provides such basic steps as data loading, verification and interpretation.
    /// </summary>
    /// <typeparam name="TRawData">A type of data to be loaded</typeparam>
    /// <typeparam name="TParsedData">A type of the analyzed data to be returned</typeparam>
    public abstract class BaseLoader<TRawData, TParsedData>
        where TRawData    : class
        where TParsedData : class
    {
        /// <summary>
        /// Loads data from a specified source, verifies it and returns its ready-to-use view.
        /// </summary>
        /// <returns>The ready-to-use view of the loaded data</returns>
        public virtual TParsedData LoadData()
        {
            TRawData rawData = LoadRawData();

            if (!IsRawDataValid(rawData))
                return default(TParsedData);

            TParsedData parsedData = ParseData(rawData);            

            return parsedData;
        }

        /// <summary>
        /// Loads data from a specified source. Has to be overridden in a derived class
        /// </summary>
        /// <returns>The loaded raw data</returns>
        protected abstract TRawData LoadRawData();

        /// <summary>
        /// Verifies the response retrieved from the specified source
        /// </summary>
        /// <param name="rawData">The raw data to be verified</param>
        /// <returns>True if the response is correct. Otherwise, False</returns>
        protected virtual bool IsRawDataValid(TRawData rawData)
        {
            return true;
        }       

        /// <summary>
        /// Analyzes the raw data and returns its ready-to-use view. Has to be overridden in a derived class
        /// </summary>
        /// <param name="rawData">The raw data</param>
        /// <returns>The ready-to-use view of the data</returns>
        protected abstract TParsedData ParseData(TRawData rawData);
    }
}

BaseLoader abstracts the loading from any source. Unlike BaseLoader, its direct descendant, HttpLoader, implies loading from a source supporting the Http protocol. See the listing of the HttpLoader class below.

HttpLoader Source

using System;
using System.Net;
using System.IO;

namespace WebLoading
{
    /// <summary>
    /// Abstracts the data loading from a specified source using Http. Provides such basic steps as data loading, verification and interpretation.
    /// </summary>
    /// <typeparam name="TRawData">A type of data to be loaded</typeparam>
    /// <typeparam name="TParsedData">A type of the analyzed data to be returned</typeparam>
    public abstract class HttpLoader<TRawData, TParsedData> : BaseLoader<TRawData, TParsedData>
        where TRawData    : class
        where TParsedData : class
    {
        /// <summary>
        /// An initial url to load data from
        /// </summary>
        protected readonly string _requestUrl;

        /// <summary>
        /// Initializes a new instance of the HttpLoader class
        /// </summary>
        /// <param name="requestUrl">An initial url to load data from</param>
        public HttpLoader(string requestUrl)
        {
            _requestUrl = requestUrl;
        }

        /// <summary>
        /// Initializes a new instance of the HttpLoader class
        /// </summary>
        public HttpLoader()
        {
        }

        /// <summary>
        /// Loads data from a specified source using Http, returns a ready-to-analyze data view
        /// </summary>
        /// <returns>A ready-to-analyze view of the data</returns>
        protected override TRawData LoadRawData()
        {
            HttpWebRequest httpWebRequest = PrepareRequest();

            TRawData rawData;
            using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
                using (Stream responseStream = httpWebResponse.GetResponseStream())
                    rawData = ReadResponseStream(responseStream);

            return rawData;
        }

        /// <summary>
        /// Creates and initializes a Http request object
        /// </summary>
        /// <returns>A Http request object</returns>
        protected virtual HttpWebRequest PrepareRequest()
        {
            string url = PrepareUrl();

            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            httpWebRequest.UserAgent      = @"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)";
            return httpWebRequest;
        }

        /// <summary>
        /// Prepares and returns an url the data should be loaded from. If the parameterless constructor is used, the method has to be overridden in a derived class
        /// </summary>
        /// <returns>An url to load the data from</returns>
        protected virtual string PrepareUrl()
        {
            return _requestUrl;
        }

        /// <summary>
        /// Reads from a stream and returns the received data in a ready-to-analyze view. Has to be overridden in a derived class
        /// </summary>
        /// <param name="stream">A data stream</param>
        /// <returns>The ready-to-analyze view of the data</returns>
        protected abstract TRawData ReadResponseStream(Stream stream);
    }
}

The next level in the hierarchy comprises such loaders as ImageLoader, XmlLoader and JsonLoader. I’ll dwell upon the last two a bit later. As regards the first one, ImageLoader is a quite simple class derived from HttpLoader that downloads an image from a specified source via Http. Being initially intended for dealing with REST formats, the loaders family can be easily extended to any data formats. So, the ImageLoader is a good example to that. The class is shown below:

ImageLoader Source and How to use

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace WebLoading
{
    /// <summary>
    /// Downloads an image from a specified source using Http
    /// </summary>
    public class ImageLoader : HttpLoader<Image, Image>
    {
        /// <summary>
        /// Initializes a new instance of the ImageLoader class
        /// </summary>
        /// <param name="url">An url to load image from</param>
        public ImageLoader(string url) : base(url)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ImageLoader class. Intended for use in a derived class
        /// </summary>
        protected ImageLoader()
        {
        }

        /// <summary>
        /// Reads an image from a stream
        /// </summary>
        /// <param name="stream">A data stream</param>
        /// <returns>An Image object</returns>
        protected override Image ReadResponseStream(Stream stream)
        {
            Image image = Image.FromStream(stream);
            return image;
        }

        /// <summary>
        /// Does nothing. Just returns a loaded image.
        /// </summary>
        /// <param name="rawData">The loaded image</param>
        /// <returns>The loaded image with no changes</returns>
        protected override Image ParseData(Image rawData)
        {
            return rawData;
        }
    }
}

//...
// "how to use" sample
ImageLoader imageLoader = new ImageLoader("http://dotnetfollower.com/wordpress/wordpress-content/uploads/2012/06/All_Site_Content_Links.png");
Image img = imageLoader.LoadData();


WebLoading Library Overview: XmlLoader

XmlLoader is an abstract class derived from HttpLoader and intended for xml data downloading. The downloaded raw xml is presented as a XDocument object. Create a descendent of the XmlLoader class to verify and turn the XDocument into any another desired view-object. The XmlLoader looks as follows:

XmlLoader Source

using System;
using System.IO;
using System.Xml.Linq;

namespace WebLoading
{
    /// <summary>
    /// Abstracts the xml data loading from a specified source using Http. Provides such basic steps as data loading, verification and interpretation.
    /// </summary>
    /// <typeparam name="TParsedData">A type of the analyzed data to be returned</typeparam>
    public abstract class XmlLoader<TParsedData> : HttpLoader<XDocument, TParsedData> where TParsedData : class
    {
        /// <summary>
        /// Initializes a new instance of the XmlLoader class
        /// </summary>
        /// <param name="url">An initial url to load data from</param>
        public XmlLoader(string url) : base(url)
        {
        }

        /// <summary>
        /// Initializes a new instance of the XmlLoader class
        /// </summary>
        public XmlLoader()
        {
        }

        /// <summary>
        /// Reads from a stream and returns the received xml data
        /// </summary>
        /// <param name="stream">A data stream</param>
        /// <returns>The received xml data</returns>
        protected override XDocument ReadResponseStream(Stream stream)
        {
            XDocument xDoc = XDocument.Load(stream);
            return xDoc;
        }
    }
}

As a simple example, below is a class-descendent demonstrating how to get the expected maximum temperatures for the next 7 days. The data source is http://graphical.weather.gov, which provides with xml data like the following:

Xml Data Sample

<?xml version="1.0"?>
<dwml version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://graphical.weather.gov/xml/DWMLgen/schema/DWML.xsd">
  <head>
    <product srsName="WGS 1984" concise-name="time-series" operational-mode="official">
      <title>NOAA's National Weather Service Forecast Data</title>
      <field>meteorological</field>
      <category>forecast</category>
      <creation-date refresh-frequency="PT1H">2012-07-13T02:13:29Z</creation-date>
    </product>
    <source>
      <more-information>http://graphical.weather.gov/xml/</more-information>
      <production-center>Meteorological Development Laboratory<sub-center>Product Generation Branch</sub-center></production-center>
      <disclaimer>http://www.nws.noaa.gov/disclaimer.html</disclaimer>
      <credit>http://www.weather.gov/</credit>
      <credit-logo>http://www.weather.gov/images/xml_logo.gif</credit-logo>
      <feedback>http://www.weather.gov/feedback.php</feedback>
    </source>
  </head>
  <data>
    <location>
      <location-key>point1</location-key>
      <point latitude="28.50" longitude="-81.37"/>
    </location>
    <moreWeatherInformation applicable-location="point1">http://forecast.weather.gov/MapClick.php?textField1=28.50&amp;textField2=-81.37</moreWeatherInformation>
    <time-layout time-coordinate="local" summarization="none">
      <layout-key>k-p24h-n7-1</layout-key>
      <start-valid-time>2012-07-13T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-13T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-14T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-14T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-15T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-15T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-16T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-16T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-17T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-17T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-18T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-18T20:00:00-04:00</end-valid-time>
      <start-valid-time>2012-07-19T08:00:00-04:00</start-valid-time>
      <end-valid-time>2012-07-19T20:00:00-04:00</end-valid-time>
    </time-layout>
    <parameters applicable-location="point1">
      <temperature type="maximum" units="Fahrenheit" time-layout="k-p24h-n7-1">
        <name>Daily Maximum Temperature</name>
        <value>92</value>
        <value>93</value>
        <value>93</value>
        <value>94</value>
        <value>92</value>
        <value>94</value>
        <value>93</value>
      </temperature>
    </parameters>
  </data>
</dwml>

The GraphicalWeatherLoader class accepts a zip code of the region you need to get forecast for. See the next listing:

GraphicalWeatherLoader Source and How to use

using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml.XPath;
using WebLoading;

namespace TestApp
{
    public class GraphicalWeatherLoader : XmlLoader<List<GraphicalWeatherLoader.DailyMaximumTemperature>>
    {
        public class DailyMaximumTemperature
        {
            public DateTime Day         { get; set; }
            public string   Temperature { get; set; }
        }

        protected static string _urlTemplate = "http://graphical.weather.gov/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgenMultiZipCode&lat=&lon=&listLatLon=&lat1=&lon1=&lat2=&lon2=&resolutionSub=&listLat1=&listLon1=&listLat2=&listLon2=&resolutionList=&endPoint1Lat=&endPoint1Lon=&endPoint2Lat=&endPoint2Lon=&listEndPoint1Lat=&listEndPoint1Lon=&listEndPoint2Lat=&listEndPoint2Lon=&zipCodeList={0}&listZipCodeList=&centerPointLat=&centerPointLon=&distanceLat=&distanceLon=&resolutionSquare=&listCenterPointLat=&listCenterPointLon=&listDistanceLat=&listDistanceLon=&listResolutionSquare=&citiesLevel=&listCitiesLevel=&sector=&gmlListLatLon=&featureType=&requestedTime=&startTime=&endTime=&compType=&propertyName=&product=time-series&begin=2004-01-01T00%3A00%3A00&end=2016-06-27T00%3A00%3A00&Unit=e&maxt=maxt&Submit=Submit";

        public GraphicalWeatherLoader(string zipCode) : base(string.Format(_urlTemplate, zipCode))
        {
        }

        protected override List<GraphicalWeatherLoader.DailyMaximumTemperature> ParseData(XDocument rawData)
        {
            List<DailyMaximumTemperature> res = new List<DailyMaximumTemperature>();

            XElement dataElement = rawData.XPathSelectElement("dwml/data");

            IEnumerator<XElement> dates        = dataElement.XPathSelectElements("time-layout/start-valid-time").GetEnumerator();
            IEnumerator<XElement> temperatures = dataElement.XPathSelectElements("parameters/temperature/value").GetEnumerator();

            while(dates.MoveNext() && temperatures.MoveNext())
            {
                DailyMaximumTemperature dailyMaximumTemperature = new DailyMaximumTemperature();
                dailyMaximumTemperature.Day         = DateTime.Parse(dates.Current.Value);
                dailyMaximumTemperature.Temperature = temperatures.Current.Value;
                res.Add(dailyMaximumTemperature);
            }            

            return res;
        }

        protected override bool IsRawDataValid(XDocument rawData)
        {
            string data = rawData.ToString();
            if(!string.IsNullOrWhiteSpace(data))
                return !data.Contains("SOAP ERROR");
            return false;
        }
    }
}

//...
// "how to use" sample
GraphicalWeatherLoader graphicalWeatherLoader = new GraphicalWeatherLoader("32801");
List<GraphicalWeatherLoader.DailyMaximumTemperature> temperatures = graphicalWeatherLoader.LoadData();
if (temperatures != null)
    foreach (GraphicalWeatherLoader.DailyMaximumTemperature temperature in temperatures)
        Console.WriteLine(string.Format("{0} - {1} Fahrenheit", temperature.Day.ToShortDateString(), temperature.Temperature));


WebLoading Library Overview: JsonLoader

Like XmlLoader, the JsonLoader class is abstract and derived from HttpLoader too. It’s dedicated for downloading json data. The downloaded json is processed by the JsonParser borrowed from the fastJSON project by Mehdi Gholam. Undoubtedly it’s the fastest json parser I’ve ever met. So, many thanks to Mehdi. The parsed json data is presented as a Dictionary<string, object>, which, besides simple objects, may contain arrays and other dictionaries nested on different levels. To have an ability to verify and transform the dictionary into something different, just create a descendent of the JsonLoader. Below is the listing of the JsonLoader:

JsonLoader Source

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using fastJSON;

namespace WebLoading
{
    /// <summary>
    /// Abstracts the json data loading from a specified source using Http. Provides such basic steps as data loading, verification and interpretation.
    /// </summary>
    /// <typeparam name="TParsedData">A type of the analyzed data to be returned</typeparam>
    public abstract class JsonLoader<TParsedData> : HttpLoader<Dictionary<string, object>, TParsedData> where TParsedData : class
    {
        /// <summary>
        /// Initializes a new instance of the JsonLoader class
        /// </summary>
        /// <param name="url">An initial url to load data from</param>
        public JsonLoader(string url) : base(url)
        {
        }

        /// <summary>
        /// Initializes a new instance of the JsonLoader class
        /// </summary>
        public JsonLoader()
        {
        }

        /// <summary>
        /// Reads json data from a stream and represents it as a tree of key-value collections
        /// </summary>
        /// <param name="stream">A data stream</param>
        /// <returns>A root key-value collection</returns>
        protected override Dictionary<string, object> ReadResponseStream(Stream stream)
        {
            StreamReader reader = new StreamReader(stream);
            string jsonText = reader.ReadToEnd();

            JsonParser parser = new JsonParser(jsonText, false);
            return parser.Decode() as Dictionary<string, object>;
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <returns>A found object</returns>
        protected static object SelectObject(Dictionary<string, object> dictionary, string path)
        {
            List<object> res = SelectObjects(dictionary, path, true, true);
            return res.Count > 0 ? res[0] : null;
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <param name="returnNull">Determines if nulls should be returned or skipped</param>
        /// <returns>A found object</returns>
        protected static List<object> SelectObjects(Dictionary<string, object> dictionary, string path, bool returnNull)
        {
            return SelectObjects(dictionary, path, returnNull, false);
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <param name="returnNull">Determines if nulls should be returned or skipped</param>
        /// <param name="onlyFirstFound">Determines if the search should be stopped after the first found object</param>
        /// <returns>A found object</returns>
        private static List<object> SelectObjects(Dictionary<string, object> dictionary, string path, bool returnNull, bool onlyFirstFound)
        {
            List<object> res = new List<object>();

            string[] pathPatterns = path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);

            List<Dictionary<string, object>> dicts = new List<Dictionary<string, object>>() { dictionary };
            for (int i = 0; i < pathPatterns.Length; i++)
            {
                string pattern = pathPatterns[i];

                Dictionary<string, object>[] dictsToProcess = dicts.ToArray();
                dicts.Clear();

                foreach (Dictionary<string, object> currentDictionary in dictsToProcess)
                {
                    if (currentDictionary.ContainsKey(pattern))
                    {
                        object obj = currentDictionary[pattern];

                        if (i == pathPatterns.Length - 1) // is it last pattern
                        {
                            if (obj != null || returnNull)
                                res.Add(obj);
                        }
                        else // move forward
                        {
                            if (obj is Dictionary<string, object>)
                                dicts.Add(obj as Dictionary<string, object>);
                            else
                                if (obj is IEnumerable)
                                    foreach (object childObj in obj as IEnumerable)
                                        if (childObj is Dictionary<string, object>)
                                            dicts.Add(childObj as Dictionary<string, object>);
                        }
                    }

                    if (onlyFirstFound && res.Count > 0)
                        break;
                }

                if (dicts.Count == 0)
                    break;
            }

            return res;
        }
    }
}

Pay attention to the SelectObjects methods that obtain an specified object(s) from a tree of arrays and dictionaries using a xpath-like path (for example, “set/items/item”).

Let’s take a look at yet another example, this time it’s a descendent of JsonLoader. The derived class is quite pointless and just gets names of some capital-cities. The http://api.geonames.org service returns the json data in the following form:

Json Data Sample

{
   "geonames":[
      {
         "fcodeName":"capital of a political entity",
         "toponymName":"Mexico City",
         "countrycode":"MX",
         "fcl":"P",
         "fclName":"city, village,...",
         "name":"Mexiko-Stadt",
         "wikipedia":"",
         "lng":-99.12766456604,
         "fcode":"PPLC",
         "geonameId":3530597,
         "lat":19.428472427036,
         "population":12294193
      },
      {
         "fcodeName":"capital of a political entity",
         "toponymName":"Manila",
         "countrycode":"PH",
         "fcl":"P",
         "fclName":"city, village,...",
         "name":"Manila",
         "wikipedia":"",
         "lng":120.9822,
         "fcode":"PPLC",
         "geonameId":1701668,
         "lat":14.6042,
         "population":10444527
      },
      {
         "fcodeName":"capital of a political entity",
         "toponymName":"Dhaka",
         "countrycode":"BD",
         "fcl":"P",
         "fclName":"city, village,...",
         "name":"Dhaka",
         "wikipedia":"",
         "lng":90.40743827819824,
         "fcode":"PPLC",
         "geonameId":1185241,
         "lat":23.710395616597037,
         "population":10356500
      }
   ]
}

The next listing is the GeoNamesCitiesLoader class:

GeoNamesCitiesLoader Source and How to use

using System;
using System.Collections;
using System.Collections.Generic;
using WebLoading;

namespace TestApp
{
    public class GeoNamesCitiesLoader : JsonLoader<List<GeoNamesCitiesLoader.City>>
    {
        public class City
        {
            public string  FCodeName   { get; set; }
            public string  ToponymName { get; set; }
            public string  CountryCode { get; set; }
            public string  Name        { get; set; }
            public string  Wikipedia   { get; set; }
            public decimal Population  { get; set; }
            public double  Longitude   { get; set; }
            public double  Latitude    { get; set; }
        }

        protected override bool IsRawDataValid(Dictionary<string, object> rawData)
        {
            return base.IsRawDataValid(rawData);
        }

        protected override string PrepareUrl()
        {
            return "http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo";
        }

        protected override List<City> ParseData(Dictionary<string, object> rawData)
        {
            List<City> res = new List<City>();

            IEnumerable arrayOfDictionaries = SelectObject(rawData, "geonames") as IEnumerable;
            foreach (Dictionary<string, object> dict in arrayOfDictionaries)
                res.Add(CreateCity(dict));

            return res;
        }

        private City CreateCity(Dictionary<string, object> metadata)
        {
            City res = new City();

            foreach (KeyValuePair<string, object> pair in metadata)
            {
                switch (pair.Key.ToLower())
                {
                    case "fcodename":   res.FCodeName   = Convert.ToString(pair.Value);  break;
                    case "toponymname": res.ToponymName = Convert.ToString(pair.Value);  break;
                    case "countrycode": res.CountryCode = Convert.ToString(pair.Value);  break;
                    case "name":        res.Name        = Convert.ToString(pair.Value);  break;
                    case "wikipedia":   res.Wikipedia   = Convert.ToString(pair.Value);  break;
                    case "lng":         res.Longitude   = Convert.ToDouble(pair.Value);  break;
                    case "lat":         res.Latitude    = Convert.ToDouble(pair.Value);  break;
                    case "population":  res.Population  = Convert.ToDecimal(pair.Value); break;
                }
            }            

            return res;
        }
    }
}

//...
// "how to use" sample
GeoNamesCitiesLoader geoNamesCitiesLoader = new GeoNamesCitiesLoader();
List<GeoNamesCitiesLoader.City> cities = geoNamesCitiesLoader.LoadData();
if (cities != null)
    foreach (GeoNamesCitiesLoader.City city in cities)
        Console.WriteLine(string.Format("{0} [Population: {1}]", city.ToponymName, city.Population));

The source code and examples are available to download here.

Categories: C#, JSON, XML Tags: , ,