Introduction

Whilst JSON is a compact and easy to read cross-language storage and data exchange format, the flexibility that it offers sometimes requires some custom handling to parse the data.

If you are not familiar with JSON, then here is a definition from the official http://www.json.org:

Quote:

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999.

Contents

Background

Why another article on JSON? This article was inspired by many questions asked in the Code Project Quick Questions & Answers section of this website. We will use a number of basic to advanced real-life examples, from EtsyFlickrMovieDBGoogle Drive, & Twitter, and solutions covering simple object serialization to custom converters and data transformations.

Tools & Libraries

Like anything, you need the right tools for the job. Here are some of the tools available including those used in this article.

Viewers & Validators

Sometimes, JSON data is packed and not very readable or we need to validate the raw data:

Code Generators

We need to create a class structure to convert the raw JSON data to. You could manually create classes from the JSON file which is a very slow and time-consuming task. There are far quicker ways to get this done. Here are a couple:

  • JSON Utils - supports both VB & C# with lots of options
  • Visual Studio Addin: Json2Csharp - Converts JSON data on your clipboard to C# classes
  • quicktype.io - supports C#, TypeScript, Go Java, Elm, Swift, Simple Types, and Schemas

Serialization Libraries

Lastly, you need to map the raw JSON data to the class structure. Again, there are a few libraries out there:

For the simple projects, all 3 libraries cover 99 ~ 100% of the requirements. For more intermediate and advanced work like custom data converters and transformations, we need Newtonsoft's Json.NET library. This will become more apparent later in this article.

Data Conversion

Once you have the raw JSON data and created the classes to map the data to, the next step will be to deserialize to classes & serialize from classes. This article will focus on deserialization. The following helper class simplifies this task.

Hide   Shrink    Copy Code
using Newtonsoft.Json;
using System.Collections.Generic; namespace Support.CSharp
{
    public static class JsonHelper
    {
        public static string FromClass<T>(T data, bool isEmptyToNull = false,
                                          JsonSerializerSettings jsonSettings = null)
        {
            string response = string.Empty;             if (!EqualityComparer<T>.Default.Equals(data, default(T)))
                response = JsonConvert.SerializeObject(data, jsonSettings);             return isEmptyToNull ? (response == "{}" ? "null" : response) : response;
        }         public static T ToClass<T>(string data, JsonSerializerSettings jsonSettings = null)
        {
            var response = default(T);             if (!string.IsNullOrEmpty(data))
                response = jsonSettings == null
                    ? JsonConvert.DeserializeObject<T>(data)
                    : JsonConvert.DeserializeObject<T>(data, jsonSettings);             return response;
        }
    }
}

We won't be using the JsonSerializerSettings. You can read more about what these are in the Newtonsoft Json.NETdocumentation.

Standard Data Types

Let us start with something simple. The following two examples work with the .Net primitive data and collection types.

Simple Object Types

Here is a Category object from the Etsy API. All JSON fields map to .Net primitive data types.

Hide   Copy Code
{
        "category_id": 68890752,
        "name": "gloves",
        "meta_title": "Handmade Gloves on Etsy - Gloves, mittens, arm warmers",
        "meta_keywords": "handmade gloves, gloves, handmade arm warmers, handmade fingerless gloves, handmade mittens, hand knit mittens, hand knit gloves, handmade accessories",
        "meta_description": "Shop for unique, handmade gloves on Etsy, a global handmade marketplace. Browse gloves, arm warmers, fingerless gloves & more from independent artisans.",
        "page_description": "Shop for unique, handmade gloves from our artisan community",
        "page_title": "Handmade gloves",
        "category_name": "accessories\/gloves",
        "short_name": "Gloves",
        "long_name": "Accessories > Gloves",
        "num_children": 3
}

We need to generate class[es] to map the JSON data to. To do this we will use JSON Utils:

Here we have selected Pascal Case naming convention. The JSON fields are all in lower case. Using the Json.NETJsonProperty attribute, mapping JSON fields to .Net class properties are quite simple. Here is the class generated:

Hide   Shrink    Copy Code
using Newtonsoft.Json;

namespace WinFormSimpleObject.Models
{
    public class Category
    {         [JsonProperty("category_id")]
        public int CategoryId { get; set; }         [JsonProperty("name")]
        public string Name { get; set; }         [JsonProperty("meta_title")]
        public string MetaTitle { get; set; }         [JsonProperty("meta_keywords")]
        public string MetaKeywords { get; set; }         [JsonProperty("meta_description")]
        public string MetaDescription { get; set; }         [JsonProperty("page_description")]
        public string PageDescription { get; set; }         [JsonProperty("page_title")]
        public string PageTitle { get; set; }         [JsonProperty("category_name")]
        public string CategoryName { get; set; }         [JsonProperty("short_name")]
        public string ShortName { get; set; }         [JsonProperty("long_name")]
        public string LongName { get; set; }         [JsonProperty("num_children")]
        public int NumChildren { get; set; }
    }
}

Now we can deserialize the JSON data into .Net class[es]:

Hide   Copy Code
public Category Result { get; set; }

private const string fileName = "Etsy_Category.Json";
private readonly string filePath = Environment.CurrentDirectory; private void GetData()
{
    // Retrieve JSON data from file
    var rawJson = File.ReadAllText(Path.Combine(filePath, fileName));     // Convert to C# Class typed object
    Result = JsonHelper.ToClass<Category>(rawJson);
}

And now can work with the data. Here is a screenshot of the attached sample app:

Simple Collection Types

The Etsy API, like many other APIs, work with not only single objects but also collections of objects wrapped in a JSON response.

Hide   Shrink    Copy Code
{
    "count": 27,
    "results": [{
        "category_id": 68890752,
        "name": "gloves",
        "meta_title": "Handmade Gloves on Etsy - Gloves, mittens, arm warmers",
        "meta_keywords": "handmade gloves, gloves, handmade arm warmers, handmade fingerless gloves, handmade mittens, hand knit mittens, hand knit gloves, handmade accessories",
        "meta_description": "Shop for unique, handmade gloves on Etsy, a global handmade marketplace. Browse gloves, arm warmers, fingerless gloves & more from independent artisans.",
        "page_description": "Shop for unique, handmade gloves from our artisan community",
        "page_title": "Handmade gloves",
        "category_name": "accessories\/gloves",
        "short_name": "Gloves",
        "long_name": "Accessories > Gloves",
        "num_children": 3
    },
    {
        "category_id": 68890784,
        "name": "mittens",
        "meta_title": "Handmade Mittens on Etsy - Mittens, gloves, arm warmers",
        "meta_keywords": "handmade mittens, handcrafted mittens, mittens, accessories, gloves, arm warmers, fingerless gloves, mittens, etsy, buy handmade, shopping",
        "meta_description": "Shop for unique, handmade mittens on Etsy, a global handmade marketplace. Browse mittens, arm warmers, fingerless gloves & more from independent artisans.",
        "page_description": "Shop for unique, handmade mittens from our artisan community",
        "page_title": "Handmade mittens",
        "category_name": "accessories\/mittens",
        "short_name": "Mittens",
        "long_name": "Accessories > Mittens",
        "num_children": 4
    }],
    "params": {
        "tag": "accessories"
    },
    "type": "Category",
    "pagination": {
        
    }
}

Here is our response wrapper:

Hide   Shrink    Copy Code
public class Pagination
{
    [JsonProperty("effective_limit")]
    public int? EffectiveLimit { get; set; }     [JsonProperty("effective_offset")]
    public int? EffectiveOffset { get; set; }     [JsonProperty("effective_page")]
    public int? EffectivePage { get; set; }     [JsonProperty("next_offset")]
    public int? NextOffset { get; set; }     [JsonProperty("next_page")]
    public int? NextPage { get; set; }
} public class Params
{
    [JsonProperty("tag")]
    public string Tag { get; set; }
} public class Response<TModel> where TModel : class
{
    [JsonProperty("count")]
    public int Count { get; set; }     [JsonProperty("results")]
    public IList<TModel> Results { get; set; }     [JsonProperty("params")]
    public Params Params { get; set; }     [JsonProperty("type")]
    public string Type { get; set; }     [JsonProperty("pagination")]
    public Pagination Pagination { get; set; }
}

Now we can deserialize the JSON data into .Net class[es]:

Hide   Copy Code
public BindingList<Category> Categories { get; set; }

private void GetData()
{
    // Retrieve JSON data from file
    var rawJson = File.ReadAllText(Path.Combine(filePath, fileName));     // Convert to C# Class typed object
    var response = JsonHelper.ToClass<Response<Category>>(rawJson);     // Get collection of objects
    if (response != null && response.Results != null && response.Results.Count > 0)
    {
        var data = response.Results;
        Categories.Clear();
        for (int i = 0; i < data.Count; i++)
        {
            Categories.Add(data[i]);
        }
    }
}

Now we can work with the data. Here is a screenshot of the attached sample app:

Non-Standard Types and Data Structure Types

Not all languages across all platforms have compatible data types. Also, providers that support multiple data formats don't always have clean translations across data formats. The next section will cover these issues and address them with simple solutions.

UNIX Epoch Timestamps

What is a UNIX epoch timestamp? According to Wikipedia.org:

Quote:

A system for describing instants in time, defined as the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970,[1][note 1] minus the number of leap seconds that have taken place since then.

Here is an example from Twitter:

Hide   Copy Code
"reset": 1502612374

And here is an example from Flickr:

Hide   Copy Code
"lastupdate": "1502528455"

We could have an integer property field and convert the integer epoch timestamp into a DateTime type post deserialization. The alternative and a better solution is to use a custom JsonConverter attribute:

Hide   Shrink    Copy Code
internal static class Unix
{
    internal static readonly DateTime Epoch = new DateTime(year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, kind: DateTimeKind.Utc);
} public static class DoubleExtensions
{
    public static DateTime FromUnixDate(this double? unixDate)
    {
        return Unix.Epoch.AddSeconds(unixDate ?? 0.0);
    }     public static DateTime FromUnixDate(this double unixDate)
    {
        return Unix.Epoch.AddSeconds(unixDate);
    }
} public sealed class JsonUnixDateConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
    }     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        double value = 0;
        if (double.TryParse(Convert.ToString(reader.Value), out value))
            return value.FromUnixDate();         if (objectType == typeof(DateTime))
            return default(DateTime);         return null;
    }     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

How it works

The JsonUnixDateConverter.CanConvert checks if it is the correct data type(s). If a match, the JsonUnixDateConverter.ReadJson executes, parses the value, converts from UNIX epoch to .Net Date type, and returns the value to be assigned to the class property.

How to Use

Simply apply the JsonUnixDateConverter to the property:

Hide   Copy Code
[JsonProperty("lastupdate"), JsonConverter(typeof(JsonUnixDateConverter))]
public DateTime? LastUpdate { get; set; }

Data Structure Types

Some data providers support multiple data formats: XML, JSON, etc... and differences in the format can create some interesting data structure types. Data structure types are where a single variable type is described as an object rather than a simple value.

Flickr has many examples of this where XML does not directly translate - XML has both attributes and elements describing data where JSON only has fields. An example of this is the Photo object and the comment count field:

Hide   Copy Code
{
    "photo": {
        "comments": {
            "_content": "483"
        }
    }
}

If we do a one-to-one translation, the classes required would be:

Hide   Copy Code
public class Comments
{
    [JsonProperty("_content")]
    public int Count { get; set; }
} public class Photo
{
    [JsonProperty("comments")]
    public Comments Comments { get; set; }
}

Then to use the above class structure:

Hide   Copy Code
int GetCommentCount(Photo photo)
{
    return photo.Comments.Count;
}

It would be much better if we could simplify the Comment count into a single integer rather than a class object:

Hide   Copy Code
int GetCommentCount(Photo photo)
{
    return photo.CommentCount;
}

Flickr has a number of other value data types that work the same. For example, the photo title field:

Hide   Copy Code
"title": {
    "_content": "North korean army Pyongyang North Korea \ubd81\ud55c"
}

The solution is a generic JsonConverter:

Hide   Shrink    Copy Code
public sealed class JsonFlickrContentConverter<TModel> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(TModel);
    }     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var result = default(TModel);         switch (reader.TokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Date:
                // convert from string or type to correct type
                // eg: "isfavorite": 0 to bool? = false
                result = (TModel)Convert.ChangeType(reader.Value, GetUnderlyingType());
                break;
            case JsonToken.StartObject:
                // sub-node _content holds actual value
                //  eg: "comments": { "_content": "483" } to int? = 483
                GetObject(reader, out result);
                break;
        }
        return result;
    }     private void GetObject(JsonReader reader, out TModel result)
    {
        var tags = JObject.Load(reader).FindTokens("_content");
        result = (tags != null && tags.Count > 0)
            ? (TModel)Convert.ChangeType((string)tags[0], GetUnderlyingType())
            : result = default(TModel);
    }     // converts Generic nullable type to underlying type
    // eg: int? to int
    private Type GetUnderlyingType()
    {
        var type = typeof(TModel);
        return Nullable.GetUnderlyingType(type) ?? type;
    }     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And to use:

Hide   Copy Code
public class Photo
{
    [JsonProperty("comments"), JsonConverter(typeof(JsonFlickrContentConverter<int?>))]
    public int? Comments { get; set; }
}

Flattening Collection Types

Like data structure types, a collection of objects also sometimes do not translate well from XML to JSON. Flickr has many examples of this. The Photo.Notes collection is a typical example:

Hide   Copy Code
"notes": {
    "note": [{
        "id": "72157613689748940",
        "author": "22994517@N02",
        "authorname": "morningbroken",
        "authorrealname": "",
        "authorispro": 0,
        "x": "227",
        "y": "172",
        "w": "66",
        "h": "31",
        "_content": "Maybe ~ I think  ...She is very happy ."
    },
    {
        "id": "72157622673125344",
        "author": "40684115@N06",
        "authorname": "Suvcon",
        "authorrealname": "",
        "authorispro": 0,
        "x": "303",
        "y": "114",
        "w": "75",
        "h": "60",
        "_content": "this guy is different."
    }]
},

Class structure would be:

Hide   Copy Code
public class Photo
{
    [JsonProperty("notes")]
    public Notes Notes { get; set; }
} public class Notes
{
    [JsonProperty("note")]
    public IList<Note> Note { get; set; }
}

As you can see, we end up with an extra unwanted Notes class to hold the list of Note.

The following generic JsonConverter will  compress this to return return a List<TModel> for the Photo class:

Hide   Shrink    Copy Code
public sealed class JsonFlickrCollectionConverter<TModel> : JsonConverter where TModel : class
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(TModel);
    }     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // A collection type containing only a collection of type
        // eg: "urls": { "url": [{...}, {...}, ...] }
        if (reader.TokenType == JsonToken.StartObject)
        {
            var jObj = JObject.Load(reader);
            if (jObj.HasValues)
            {
                var items = new List<TModel>();
                foreach (var value in jObj.Values())
                {
                    try
                    {
                        items.AddRange(JsonHelper.ToClass<IList<TModel>>(value.ToString()));
                    }
                    catch (Exception)
                    {
                        // unexpected type
                        return null;
                    }
                }
                return items;
            }
        }
        return null;
    }     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And to use:

Hide   Copy Code
[JsonProperty("notes"), JsonConverter(typeof(JsonFlickrCollectionConverter<Note>))]
public IList<Note> Notes { get; set; }

Multi-Value Type Collections

This is where a collection of objects returned are of similar or different complex object types. themoviedb.org Multi search returns TVMove, and Person types in the same collection. Google Drive is another example that returns a collection with File and Folder complex object types.

MovieDB.Org

Here is a screenshot of the included MovieDB demo app that shows an example of this:

The JSON data for the above screenshot looks like this:

Hide   Shrink    Copy Code
{
    "page": 1,
    "total_results": 3433,
    "total_pages": 172,
  "results": [
    {
      "original_name": "Captain N and the New Super Mario World",
      "id": 26732,
      "media_type": "tv",
      "name": "Captain N and the New Super Mario World",
      "vote_count": 2,
      "vote_average": 3.5,
      "poster_path": "/i4Q8a0Ax5I0h6b1rHOcQEZNvJzG.jpg",
      "first_air_date": "1991-09-14",
      "popularity": 1.479857,
      "genre_ids": [
        16,
        35
      ],
      "original_language": "en",
      "backdrop_path": "/iYT5w3Osv3Bg1NUZdN9UYmVatPs.jpg",
      "overview": "Super Mario World is an American animated television series loosely based on the Super NES video game of the same name. It is the third and last Saturday morning cartoon based on the Super Mario Bros. NES and Super NES series of video games. The show only aired 13 episodes due to Captain N: The Game Master's cancellation on NBC. Just like The Adventures of Super Mario Bros. 3, the series is produced by DIC Entertainment and Reteitalia S.P.A in association with Nintendo, who provided the characters and power-ups from the game.",
      "origin_country": [
        "US"
      ]
    },
    {
      "popularity": 1.52,
      "media_type": "person",
      "id": 1435599,
      "profile_path": null,
      "name": "Small World",
      "known_for": [
        {
          "vote_average": 8,
          "vote_count": 1,
          "id": 329083,
          "video": false,
          "media_type": "movie",
          "title": "One For The Road: Ronnie Lane Memorial Concert",
          "popularity": 1.062345,
          "poster_path": "/i8Ystwg81C3g9a5z3ppt3yO1vkS.jpg",
          "original_language": "en",
          "original_title": "One For The Road: Ronnie Lane Memorial Concert",
          "genre_ids": [
            10402
          ],
          "backdrop_path": "/oG9uoxtSuokJBgGO4XdC5m4uRGU.jpg",
          "adult": false,
          "overview": "At The Royal Albert Hall, London on 8th April 2004 after some 15 months of planning with Paul Weller, Ronnie Wood, Pete Townshend, Steve Ellis, Midge Ure, Ocean Colour Scene amongst them artists assembled to perform to a sell-out venue and to pay tribute to a man who co-wrote many Mod anthems such as \"\"Itchycoo Park, All Or Nothing, Here Comes The Nice, My Mind's Eye\"\" to name just a few. Ronnie Lane was the creative heart of two of Rock n Rolls quintessentially English groups, firstly during the 60's with The Small Faces then during the 70;s with The Faces. After the split of the Faces he then formed Slim Chance and toured the UK in a giant circus tent as well as working in the studio with Eric Clapton, Pete Townshend and Ronnie Wood. 5,500 fans looked on in awe at The R.A.H as the superb evening's entertainment ended with \"\"All Or Nothing\"\" featuring a surprise appearance by Chris Farlowe on lead vocals.",
          "release_date": "2004-09-24"
        }
      ],
      "adult": false
    },
    {
      "vote_average": 6.8,
      "vote_count": 4429,
      "id": 76338,
      "video": false,
      "media_type": "movie",
      "title": "Thor: The Dark World",
      "popularity": 10.10431,
      "poster_path": "/bnX5PqAdQZRXSw3aX3DutDcdso5.jpg",
      "original_language": "en",
      "original_title": "Thor: The Dark World",
      "genre_ids": [
        28,
        12,
        14
      ],
      "backdrop_path": "/3FweBee0xZoY77uO1bhUOlQorNH.jpg",
      "adult": false,
      "overview": "Thor fights to restore order across the cosmos… but an ancient race led by the vengeful Malekith returns to plunge the universe back into darkness. Faced with an enemy that even Odin and Asgard cannot withstand, Thor must embark on his most perilous and personal journey yet, one that will reunite him with Jane Foster and force him to sacrifice everything to save us all.",
      "release_date": "2013-10-29"
    }
  ]
}

The key to identifying the different complex data types is with a key field. In the above MovieDB JSON data example, the key field is media_type.

When we define the classes for the 3 data types TV, Movie, and Person, we will use a simple Interface against each class type for the collection:

Hide   Copy Code
public interface IDataType
{
}

A custom JsonConverter JsonDataTypeConverter is used on the collection. It identifies the object type and generates the appropriate class and populates the fields:

Hide   Shrink    Copy Code
public sealed class JsonDataTypeConverter : JsonConverter
{
    // destination data type
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IDataType);
    }     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        List<IDataType> results = null;         // Are there multiple results?
        if (reader.TokenType== JsonToken.StartArray)
        {
            // Retrieve a list of Json objects
            var jObjs = serializer.Deserialize<IList<JObject>>(reader);
            if (jObjs != null && jObjs.Count > 0)
            {
                results = new List<IDataType>();                 for (int i = 0; i < jObjs.Count; i++)
                {
                    // Does the object contain a "media_type" identifier - eg: "media_type":
                    var token = jObjs[i].FindTokens("media_type");
                    if (token != null && token.Count > 0)
                    {
                        // Only one is expected
                        switch (token[0].ToString())
                        {
                            // "media_type": "tv"
                            case "tv":
                                results.Add(Convert<TV>(jObjs[i]));
                                break;
                            // "media_type": "movie"
                            case "movie":
                                results.Add(Convert<Movie>(jObjs[i]));
                                break;
                            // "media_type": "person"
                            case "person":
                                results.Add(Convert<Person>(jObjs[i]));
                                break;
                        }
                    }
                }
            }
        }
        return results;
    }     // Convert Json Object data into a specified class type
    private TModel Convert<TModel>(JObject jObj) where TModel : IDataType
    {
        return JsonHelper.ToClass<TModel>(jObj.ToString());
    }     // one way conversion, so back to Json is not required
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And to use:

Hide   Copy Code
public class Response
{
    [JsonProperty("results"), JsonConverter(typeof(JsonDataTypeConverter))]
    public IList<IDataType> Results { get; set; }
}

Below is a screenshot of the collection when viewing the IntelliSense debug window showing the collection with multiple object types:

Google Drive

Another example of this is Google Drive API. The File type, for example, has two identifiers: one to differentiate between Files and Folder, and another for the type of File - Jpg, Png, Text file, Document, MP4, etc. This can be seen in the JSON data.

Hide   Shrink    Copy Code
{
 "kind": "drive#fileList",
 "incompleteSearch": false,
 "files": [
  {
   "kind": "drive#file",
   "mimeType": "video/mp4"
  },
  {
   "kind": "drive#file",
   "mimeType": "application/vnd.google-apps.folder"
  },
  {
   "kind": "drive#file",
   "mimeType": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
  },
  {
   "kind": "drive#file",
   "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  },
  {
   "kind": "drive#file",
   "mimeType": "text/plain"
  },
  {
   "kind": "drive#file",
   "mimeType": "image/png"
  }
 ]
}

The different File types can be seen reflected in the following sample application screenshot:

The sample app above works with a Hierarchical data structure and loads each branch's data on-demand as it is opened.

All the different types of files have the same data. So we can declare a base type and inherit it for all the different types of files.

Hide   Shrink    Copy Code
public class File : IResourceKind
{
    [JsonProperty("kind")]
    public string Kind { get; set; }     [JsonProperty("mimeType")]
    public string MimeType { get; set; }
} public class Folder : File
{
} public class TxtDocument : File
{
} public class WordDocument : File
{
} public class PngImage : File
{
} public class JpgImage : File
{
} public class Zipped : File
{
}

Side Note: When when working with Implicit templates in XAML, you can define a default template, in this case for the base File Type, and if an implicit template is not found for the data type (class), the base template is applied for the inherited base class type.

Hide   Copy Code
<DataTemplate DataType="{x:Type m:File}">
    <!-- Template here -->
</DataTemplate>

As there is a common base type for a large number of File types, a compact handler inside a customer JsonConverter, JsonDataTypeConverter for Google Drive, can be used. Here we will have a lookup Dictionary Table for the "mime_type" JSON field and method reference with conversion type.

Hide   Shrink    Copy Code
// only some types are lists for briefity
private readonly Dictionary<string, Func<JObject, File>> mimeTypes
  = new Dictionary<string, Func<JObject, File>>
{
    { "application/vnd.google-apps.folder", Convert<Folder>() },
    { "image/jpeg", Convert<JpgImage>() },
    { "image/png", Convert<PngImage>() },
    { "application/zip", Convert<Zipped>() },
    { "application/x-zip-compressed", Convert<Zipped>() },
    { "video/mp4", Convert<Mp4Video>() },
    { "text/plain", Convert<TxtDocument>() },
    { "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  Convert<PptDocument>() },
    { "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  Convert<WordDocument>() }
}; // Convert file of type.... mimeTypes
private void ProcessFileType(List<File> results, IList<JObject> jObjs, int i)
{
    var fileToken = jObjs[i].FindTokens("mimeType");
    if (fileToken != null && fileToken.Count > 0)
    {
        var key = mimeTypes.Keys.FirstOrDefault(x => x.Equals(fileToken[0].ToString()));
        if (key != null)
        {
            results.Add(mimeTypes[key](jObjs[i]));
        }
    }
} // Convert Json Object data into a specified class type
private static Func<JObject, File> Convert<TModel>() where TModel : File
{
    return (jObj) => JsonHelper.ToClass<TModel>(jObj.ToString());
}

The IntelliSense debug window for the File collection property correctly reflects the recursive deserialization of the different File types:

Recursive Deserialization

JSON object structures can be many node levels deep. Each node could have properties with their own custom JsonConverters. An example of this is the MovieDB above in the previous section Multi-Value Type Collections. Here is an extract for the Person JSON node structure with the node collection known_for :

Hide   Copy Code
{
    "page": 1,
    "total_results": 3433,
    "total_pages": 172,
  "results": [
    {
      "media_type": "tv",
    },
    {
      "media_type": "person",
      "known_for": [
        {
          "media_type": "movie",
        }
      ]
    },
    {
      "media_type": "movie",
    }
  ]
}

The JsonDataTypeConverter class above already supports recursive deserialization. Once the node object type is identified, we re-call the JsonHelper.ToClass<...>(...) method:

Hide   Copy Code
// Convert Json Object data into a specified class type
private TModel Convert<TModel>(JObject jObj) where TModel : IDataType
{
    return JsonHelper.ToClass<TModel>(jObj.ToString());
}

And in the Person class, we apply the JsonConverter attribute JsonDataTypeConverter:

Hide   Copy Code
public class Person : RecordBase
{
    [JsonProperty("known_for"), JsonConverter(typeof(JsonDataTypeConverter))]
    public IList<IDataType> KnownFor { get; set; }
}

Here, we can see from the IntelliSense debug window for the Person.KnownFor collection property and correctly reflects the recursive deserialization of the Movie class type:

Data Transformation

Normally, to transform data, it is a two-step process. First, we convert the JSON data to .Net classes (34 classes in total in this case), then transform the data.

The last example that I have demonstrates how to transform the JSON data to a custom class collection in a single step. By doing this, we have reduced the number of classes required, in this case, from 34 to 4!

Here is what the end result will look like:

As there are too many data classes to post here, I will only discuss the JsonConverter used.  You can view and run the sample project,WpfApplicationRateLimitStatus which is included in the download. I have included both the standard and custom mapping classes.

The JsonApiRateLimitsConverter compresses the multiple classes into a simpler collection of data that is compatible with the application's requirements.

Hide   Shrink    Copy Code
public sealed class JsonApiRateLimitsConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(RateCategoryModel);
    }     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        ObservableCollection<RateCategoryModel> result = null;         // is this the start of an object?
        if (reader.TokenType == JsonToken.StartObject)
        {
            var jObj = JObject.Load(reader);
            if (jObj.HasValues)
            {
                // Step through each top property node
                result = new ObservableCollection<RateCategoryModel>();
                foreach (var child in jObj.Children())
                {
                    var rate = ProcessChild(child, serializer);
                    if (rate != null) result.Add(rate);
                }
            }
        }
        return result;
    }     private static RateCategoryModel ProcessChild(JToken child, JsonSerializer serializer)
    {
        RateCategoryModel rate = null;
        var node = serializer.Deserialize<JProperty>(child.CreateReader());
        if (node.HasValues)
        {
            // step through each sub-property node
            rate = new RateCategoryModel { Name = node.Name };
            foreach (var item in node.Values())
            {
                var limit = serializer.Deserialize<JProperty>(item.CreateReader());
                rate.Limits.Add(new RateLimitModel
                {
                    // we only want the last part of the property name
                    Name = GetPropertyName(limit),
                    Limit = GetRateLimit(limit)
                });
            }
        }
        return rate;
    }     private static string GetPropertyName(JProperty prop)
    {
        string name = string.Empty;         if (!string.IsNullOrEmpty(prop.Name))
        {
            var parts = prop.Name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            name = string.Join("_", parts.Skip(1)).Replace(":", "");
            if (string.IsNullOrEmpty(name))
                name = prop.Name.Replace("/", "");
        }
        else
        {
            name = "__noname__";
        }         return name;
    }     private static ApiRateLimitModel GetRateLimit(JProperty limit)
    {
        ApiRateLimitModel model = null;         if (!string.IsNullOrEmpty(limit.First.ToString()))
        {
            var rawJson = limit.First.ToString();
            model = JsonHelper.ToClass<ApiRateLimitModel>(rawJson);
        }         return model;
    }     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And to use:

Hide   Copy Code
public class APIRateStatusModel
{
    [JsonProperty("resources"), JsonConverter(typeof(JsonApiRateLimitsConverter))]
    public ObservableCollection<RateCategoryModel> Resources { get; set; }
}
Hide   Copy Code
private void GetData()
{
    // Retrieve JSON data from file
    var rawJson = File.ReadAllText(Path.Combine(filePath, fileName));     // Convert to C# Class List of typed objects
    Result = JsonHelper.ToClass<APIRateStatusModel>(rawJson);
}

Summary

We have covered how to work with standard value types, custom value types, compress structures to types, and transform from the raw JSON data structure to class structures that support the needs of our applications. Newtonsoft's Json.NET, the Helper classes, and the custom JsonConverters makes working with JSON data clean and very easy.

Sample Applications

There are six (6) sample applications, both in C# and VB, included in the download:

  1. WinFormSimpleObject - Etsy Category

    • WinForm, code-behind
  2. WinFormSimpleCollection - Etsy Categories
    • WinForm, Datbinding, MVVM (simple)
  3. WpfPhoto - Flickr Photo Viewer
    • WPF, MVVM
    • JsonFlickrCollectionConverter, JsonFlickrContentConverter, JsonFlickrUnixDateContentConverter, JsonFlickrUriContentConverter, StringEnumConverter
  4. WpfMultiSearch - MovieDB MultiSearch result
    • WPF, MVVM, Implicit Templates
    • JsonDataTypeConverter, JsonPartialUrlConverter
  5. WpfApplicationRateLimitStatus - Twitter JSON data Transformation
    • WPF, MVVM, DataGrid
    • JsonUnixDateConverter, JsonApiRateLimitsConverter
  6. WpfFileExplorer - Google Drive File Explorer
    • WPF, MVVM, TreeView with load-on-demand & custom template, Implicit Templates, Hierarchical DataBinding
    • JsonDataTypeConverter, JsonGoogleUriContentConverter,

A download link for all the samples is provided above.

History

v1.0  - August 15, 2017 - Initial Release

v1.1 - August 16, 2017 - Added reference to Fiddler in the Viewers & Validators section

v1.2 - August 16, 2017 - Added new Recursive Deserialization section (thanks BillWoodruff for the suggestion); new download with minor update to C#/VB WpfMultiSearch sample

v1.3 - August 19, 2017 - Added new Google Drive File Explorer sample app to demonstrate another version of Multi-Value Type Collections with mutiple identifier keys for file type class support broken down in the Multi-Value Type Collections section

v1.4 - October 26, 2017 - Added table of contents for easy navigation; added code generator

Working with JSON in C# & VB的更多相关文章

  1. 试用 ModVB(一):安装教程+使用 JSON 常量和 JSON 模式匹配

    前排提醒:阅读此文章并充分尝试 ModVB 的新语法需要较长的时间.对于程序员而言,如果你工作时不用 VB,则最好避免在上班时间看,以免被领导认为你在长时间摸鱼. 什么是 ModVB ModVB 是一 ...

  2. VB将JSON映射到表格实现解析

    现在抓取网页数据的时候,经常会遇到JSON的数据,相对于繁杂无标签名的HTML源,用JSON传回的数据比较直观好看点.但是从其中提炼数据也让人觉得很烦躁,基本上就是不断的查找,截取,或者组装成JS代码 ...

  3. VB 老旧版本维护系列---尴尬的webapi访问返回json对象

    尴尬的webapi访问返回json对象 首先Imports Newtonsoft.Json Imports MSXML2(Interop.MSXML2.dll) Dim URLEncode As Sy ...

  4. 用VB实现SmartQQ机器人

    这里为了便于介绍程序设计的流程,更多以代码形式给出,具体可用火狐浏览器的firebug插件来抓包分析,或者用谷歌浏览器的开发者工具进行抓包.抓包地址是:http://w.qq.com 第一步,是二维码 ...

  5. [Network] HTML、XML和JSON学习汇总

    写在前面:楼主也是刚刚接触这方面的知识,之前完全是零基础,后来经朋友推荐了几个不错的博文,看完以后豁然开朗.但是此博文更加偏重于基础知识介绍(其实更深的楼主也还不了解,这方面的大神请绕道),只是分享个 ...

  6. csc.rsp Nuget MVC/WebAPI、SignalR、Rx、Json、EntityFramework、OAuth、Spatial

    # This file contains command-line options that the C# # command line compiler (CSC) will process as ...

  7. csc.rsp Nuget MVC/WebAPI 5.0、SignalR 2.0、Rx、Json、Azure、EntityFramework、OAuth、Spatial

    # This file contains command-line options that the C# # command line compiler (CSC) will process as ...

  8. java中Array/List/Map/Object与Json互相转换详解

    http://blog.csdn.net/xiaomu709421487/article/details/51456705 JSON(JavaScript Object Notation): 是一种轻 ...

  9. Json格式转换

    验证Json格式可以进入 http://json.cn/ json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构1.对象:对象 ...

随机推荐

  1. 【angularJS】三个学习angulaJS的链接

    1.官方文档:https://code.angularjs.org/1.5.7/docs/api 2.A Better Way to Learn AngularJS:https://thinkster ...

  2. mapreduce实现搜索引擎简单的倒排索引

    使用hadoop版本为2.2.0 倒排索引简单的可以理解为全文检索某个词 例如:在a.txt 和b.txt两篇文章分别中查找统计hello这个单词出现的次数,出现次数越多,和关键词的吻合度就越高 现有 ...

  3. asp.net购物车,订单以及模拟支付宝支付(二)---订单表

    购物车准备完毕之后,就要着手订单表的设计了 表结构如下: T_Orders T_OrderBooks 为什么这里要分为两个表? 仔细想想,现实生活中的发票 特地去网上找了一张,不是很清晰 但是,正常人 ...

  4. Mybatis学习记录(一)---- 简单的CRUD

    1 mybatis是什么? mybatis是一个持久层的框架,是apache下的顶级项目. mybatis托管到googlecode下,再后来托管到github下(https://github.com ...

  5. Mysql的Root密码忘记,查看或修改的解决方法(图文介绍)

    http://www.jb51.net/article/38473.htm 首先启动命令行 1.在命令行运行:taskkill /f /im mysqld-nt.exe 下面的操作是操作mysql中b ...

  6. jsp+servlet实现文件上传

    上传(上传不能使用BaseServlet) 1. 上传对表单限制 * method="post" * enctype="multipart/form-data" ...

  7. ajax--百度百科

    AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. AJAX = 异步 JavaScript和 ...

  8. Spring属性占位符 PropertyPlaceholderConfigurer

    http://www.cnblogs.com/yl2755/archive/2012/05/06/2486752.html PropertyPlaceholderConfigurer是个bean工厂后 ...

  9. 固态继电器SSR

    s107.  LH1521.  CPC1035N http://e22a.com/h.bXsDYw?cv=AAOzhSfJ&sm=53e30b

  10. robot.txt 文件 作用和语法

    seo工作者应该不陌生,robots.txt文件是每一个搜索引擎蜘蛛到你的网站之后要寻找和访问的第一个文件,robots.txt是你对搜索引擎制定的一个如何索引你的网站的规则.通过该文件,搜索引擎就可 ...