views:

85

answers:

3

I need to determine if the DataContractJsonSerializer can deserialize JSON to a Dictionary if the Dictionary is not the wrapping object being deserialized. I know there are other open source codes projects that may be able to do this (json.net, json-framework) but I'd like to know definitively if .NET can natively handle this before taking that approach. all the MSDN documentation suggests it can but I am not sure if I am handling the conversion correctly.

So to start I'm receiving a video object from a web service in JSON like this:

{"videos":
 [{
  "id":000000000000,
  "name":"Video Title",
  "adKeys":null,
  "shortDescription":"short description",
  "longDescription":"long description",
  "creationDate":"1282819270274",
  "publishedDate":"1282819270274",
  "lastModifiedDate":"1282857505013",
  "linkURL":null,
  "linkText":null,
  "tags":["tag1","tag2","tag3","tag4","tag5","tag5"],
  "videoStillURL":"",
  "thumbnailURL":"",
  "referenceId":"0000",
  "length":177679,
  "economics":"AD_SUPPORTED",
  "playsTotal":178,
  "playsTrailingWeek":178,
  "customFields":{"custom title":"custom value", "custom title 2":"custom value2"},
  "FLVURL":"",
  "renditions":[]
 }],
"page_number":0,
"page_size":0,
"total_count":1}

I am deserializing this object as a BCQueryResult. The BCQueryResult has a property called Videos that is a BCCollection which extends List and applies a CollectionDataContract. The video object in turn has a property called customFields that is a CustomFields object which extends Dictionary and applies a CollectionDataContract. All collection types that are List types(videos, tags, cuepoints etc.) are deserialized without issue. The Dictionary type is the only type that has a problem deserializing. There is no error but even when a value is present the result is empty. If I strip out all date types and deserialize with the JavaScriptSerializer it will give me the proper value, but because of issues with required field types I cannot use the JavaScriptSerializer instead I am using the DataContractJsonSerializer. I've attached the classes below.

BCQueryResult:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using BrightcoveSDK.Media;

namespace BrightcoveSDK
{
 [DataContract]
 public class BCQueryResult
 {
  [DataMember(Name = "videos")]
  public BCCollection<BCVideo> Videos;
  [DataMember(Name = "playlists")]
  public BCCollection<BCPlaylist> Playlists;
  [DataMember(Name = "page_number")]
  public int PageNumber { get; set; }
  [DataMember(Name = "page_size")]
  public int PageSize { get; set; }
  [DataMember(Name = "total_count")]
  public int TotalCount { get; set; }

  public int MaxToGet = 0;
  public List<QueryResultPair> QueryResults = new List<QueryResultPair>();

  public BCQueryResult() {
   Playlists = new BCCollection<BCPlaylist>();
   Videos = new BCCollection<BCVideo>();
   PageNumber = 0;
   PageSize = 0;
   TotalCount = 0;
  }

  public void Merge(BCQueryResult qr) {

   //if (qr.QueryResults != null && qr.QueryResults.Count > 0)
   //        QueryResults.Add(qr.QueryResults[qr.QueryResults.Count -1]);
   if (qr.Videos != null) Videos.AddRange(qr.Videos);
   if(qr.Playlists != null) Playlists.AddRange(qr.Playlists);
   PageNumber = qr.PageNumber;
   TotalCount = qr.TotalCount;
   PageSize = qr.PageSize;
  }
 }
}

BCCollection:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace BrightcoveSDK
{
 [CollectionDataContract]
 public class BCCollection<T> : List<T>
 {}

 public static class BCCollectionExtensions {

  public static string ToDelimitedString(this BCCollection<string> list, string Delimiter) {

   string r = "";
   foreach (string s in list) {
    if (r.Length > 0) {
     r += Delimiter;
    }
    r += s;
   }
   return r;
  }
 }
}

BCVideo:

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Web;
using System.Runtime.Serialization;

namespace BrightcoveSDK.Media
{
 /// <summary>
 /// The Video object is an aggregation of metadata and asset information associated with a video
 /// </summary>
 [DataContract]
 public class BCVideo : BCObject, IComparable<BCVideo>
 {
  /// <summary>
  /// A number that uniquely identifies this Video, assigned by Brightcove when the Video is created.
  /// </summary>
  [DataMember]
  public long id { get; set; }

  /// <summary>
  /// The title of this Video.
  /// </summary> 
  [DataMember]
  public string name { get; set; }

  /// <summary>
  /// A short description describing this Video, limited to 256 characters.
  /// </summary> 
  [DataMember]
  public string shortDescription { get; set; }

  /// <summary>
  /// A longer description of this Video, bounded by a 1024 character limit.
  /// </summary> 
  [DataMember]
  public string longDescription { get; set; }

  [DataMember(Name = "creationDate")]
  private string createDate { get; set; }

  /// <summary>
  /// The date this Video was created, represented as the number of milliseconds since the Unix epoch.
  /// </summary> 
  public DateTime creationDate {
   get {
    return DateFromUnix(createDate);
   }
   set {
    createDate = DateToUnix(value);
   }
  }

  [DataMember(Name = "publishedDate")]
  private string publishDate { get; set; }

  /// <summary>
  /// The date this Video was last made active, represented as the number of milliseconds since the Unix epoch.
  /// </summary> 
  public DateTime publishedDate {
   get {
    return DateFromUnix(publishDate);
   }
   set {
    publishDate = DateToUnix(value);
   }
  }

  [DataMember(Name = "lastModifiedDate")]
  private string modifyDate { get; set; }

  /// <summary>
  /// The date this Video was last modified, represented as the number of milliseconds since the Unix epoch.
  /// </summary> 
  public DateTime lastModifiedDate {
   get {
    return DateFromUnix(modifyDate);
   }
   set {
    modifyDate = DateToUnix(value);
   }
  }

  /// <summary>
  /// An optional URL to a related item.
  /// </summary> 
  [DataMember]
  public string linkURL { get; set; }

  /// <summary>
  /// The text displayed for the linkURL.
  /// </summary> 
  [DataMember]
  public string linkText { get; set; }

  [DataMember(Name = "FLVURL")]
  public string flvURL { get; set; }

        private BCCollection<string> _tags;

  /// <summary>
  /// A list of Strings representing the tags assigned to this Video.
  /// </summary> 
        [DataMember]
        public BCCollection<string> tags {
            get {
                if (_tags == null) {
                    _tags = new BCCollection<string>();
                }
                return _tags;
            }
            set {
                _tags = value;
            }
        }

        private BCCollection<BCCuePoint> _cuePoints;

        /// <summary>
        /// A list of cuePoints representing the cue points assigned to this Video.
        /// </summary> 
        [DataMember]
        public BCCollection<BCCuePoint> cuePoints {
            get {
                if(_cuePoints == null){
                    _cuePoints = new BCCollection<BCCuePoint>();
                }
                return _cuePoints;
            }
            set {
                _cuePoints = value;
            }
        }

  /// <summary>
  /// The URL to the video still image associated with this Video. Video stills are 480x360 pixels.
  /// </summary> 
  [DataMember]
  public string videoStillURL { get; set; }

  /// <summary>
  /// The URL to the thumbnail image associated with this Video. Thumbnails are 120x90 pixels.
  /// </summary> 
  [DataMember]
  public string thumbnailURL { get; set; }

  /// <summary>
  /// A user-specified id that uniquely identifies this Video. ReferenceID can be used as a foreign-key to identify this video in another system. 
  /// </summary> 
  [DataMember]
  public string referenceId { get; set; }

  /// <summary>
  /// The length of this video in milliseconds.
  /// </summary> 
  [DataMember]
  public string length { get; set; }

  [DataMember(Name = "economics")]
  private string ecs { get; set; }

  /// <summary>
  /// Either FREE or AD_SUPPORTED. AD_SUPPORTED means that ad requests are enabled for this Video.
  /// </summary> 
  public BCVideoEconomics economics {
   get {
    if (ecs == null) {
     return BCVideoEconomics.AD_SUPPORTED;
    }
    else if (ecs.Equals(BCVideoEconomics.AD_SUPPORTED.ToString())) {
     return BCVideoEconomics.AD_SUPPORTED;
    }
    else if (ecs.Equals(BCVideoEconomics.FREE.ToString())) {
     return BCVideoEconomics.FREE;
    }
    else {
     return BCVideoEconomics.AD_SUPPORTED;
    }
   }
   set {
    ecs = value.ToString();
   }
  }

  [DataMember(Name = "playsTotal")]
  private string plays { get; set; }

  /// <summary>
  /// How many times this Video has been played since its creation.
  /// </summary> 
  public long playsTotal {
   get {
    if (!String.IsNullOrEmpty(plays)) {
     return long.Parse(plays);
    } else {
     return 0;
    }
   }
   set {
    plays = value.ToString();
   }
  }

  [DataMember(Name = "playsTrailingWeek")]
  private string playsWeek { get; set; }

        public static List<string> VideoFields {
            get {
                List<string> fields = new List<string>();
                foreach (string s in Enum.GetNames(typeof(BrightcoveSDK.VideoFields))) {
                    fields.Add(s);
                }
                return fields;
            }
        }

  /// <summary>
  /// How many times this Video has been played within the past seven days, exclusive of today.
  /// </summary> 
  public long playsTrailingWeek {
   get {
    if(!String.IsNullOrEmpty(playsWeek)) {
     return long.Parse(playsWeek);
    } else {
     return 0;
    }
   }
   set {
    playsWeek = value.ToString();
   }
  }

        [DataMember(Name = "itemState")]
        private string _itemState {get; set;}

        public ItemStateEnum itemState {
            get {
    if (_itemState == null) {
     return ItemStateEnum.ACTIVE;
    }
    else if (_itemState.Equals(ItemStateEnum.ACTIVE.ToString())) {
     return ItemStateEnum.ACTIVE;
    }
    else if (_itemState.Equals(ItemStateEnum.DELETED.ToString())) {
     return ItemStateEnum.DELETED;
    }
                else if (_itemState.Equals(ItemStateEnum.INACTIVE.ToString())) {
     return ItemStateEnum.INACTIVE;
    }
    else {
     return ItemStateEnum.ACTIVE;
    }
   }
   set {
    ecs = value.ToString();
   }
        }

        [DataMember(Name = "version")]
        private string _version {get; set;}

        public long version {
         get {
    if (!String.IsNullOrEmpty(_version)) {
     return long.Parse(_version);
    } else {
     return 0;
    }
   }
   set {
    _version = value.ToString();
   }
        }

        [DataMember]
        public string submissionInfo {get; set;}

        public CustomFields _customFields;

        [DataMember]
        public CustomFields customFields {
            get {
                if (_customFields == null) {
                    _customFields = new CustomFields();
                }
                return _customFields;
            }
            set {
                _customFields = value;
            }
        }

        [DataMember]
        public string releaseDate {get; set;}

        [DataMember]
        public string geoFiltered {get; set;}

        [DataMember]
        public string geoRestricted {get; set;}

        [DataMember]
        public string geoFilterExclude {get; set;}

        [DataMember]
        public string excludeListedCountries {get; set;}

        private BCCollection<string> _geoFilteredCountries;

        [DataMember]
        public BCCollection<string> geoFilteredCountries {
            get {
                if (_geoFilteredCountries == null) {
                    _geoFilteredCountries = new BCCollection<string>();
                }
                return _geoFilteredCountries;
            }
            set {
                _geoFilteredCountries = value;
            }
        }

        private BCCollection<string> _allowedCountries;

        [DataMember]
        public BCCollection<string> allowedCountries {
            get {
                if (_allowedCountries == null) {
                    _allowedCountries = new BCCollection<string>();
                }
                return _allowedCountries;
            }
            set {
                _allowedCountries = value;
            }
        }

        [DataMember(Name = "accountId")]
        private string _accountId {get; set;}

        public long accountId {
         get {
    if (!String.IsNullOrEmpty(_accountId)) {
     return long.Parse(_accountId);
    } else {
     return 0;
    }
   }
   set {
    _accountId = value.ToString();
   }
        }

  public BCVideo() {
  }

  #region IComparable Comparators

  public int CompareTo(BCVideo other) {
   return name.CompareTo(other.name);
  }

  //CREATION_DATE
  public static Comparison<BCVideo> CreationDateComparison =
   delegate(BCVideo v1, BCVideo v2)
   {
    return v1.creationDate.CompareTo(v2.creationDate);
   };

  //PLAYS_TOTAL
  public static Comparison<BCVideo> TotalPlaysComparison =
   delegate(BCVideo v1, BCVideo v2)
   {
    return v1.playsTotal.CompareTo(v2.playsTotal);
   };

  //PUBLISH_DATE
  public static Comparison<BCVideo> PublishDateComparison =
   delegate(BCVideo v1, BCVideo v2)
   {
    return v1.publishedDate.CompareTo(v2.publishedDate);
   };

  //MODIFIED_DATE
  public static Comparison<BCVideo> ModifiedDateComparison =
   delegate(BCVideo v1, BCVideo v2)
   {
    return v1.lastModifiedDate.CompareTo(v2.lastModifiedDate);
   };

  //PLAYS_TRAILING_WEEK
  public static Comparison<BCVideo> PlaysTrailingComparison =
   delegate(BCVideo v1, BCVideo v2)
   {
    return v1.playsTrailingWeek.CompareTo(v2.playsTrailingWeek);
   };

  #endregion
 }

 public static class BCVideoExtensions {

  #region Extension Methods

  public static string ToCreateJSON(this BCVideo video) {
   return ToJSON(video, JSONType.Create);
  }

  public static string ToJSON(this BCVideo video) {
   return ToJSON(video, JSONType.Update);
  }

        private static string ToJSON(this BCVideo video, JSONType type) {

   //--Build Video in JSON -------------------------------------//

            StringBuilder jsonVideo = new StringBuilder();
            jsonVideo.Append("{");

   if(type.Equals(JSONType.Update)){
    //id
    jsonVideo.Append("\"id\": " + video.id.ToString() + ",");
   }

   //name
   if (!string.IsNullOrEmpty(video.name)) {
    jsonVideo.Append("\"name\": \"" + video.name + "\"");
   }

   //shortDescription
   if (!string.IsNullOrEmpty(video.shortDescription)) {
    jsonVideo.Append(",\"shortDescription\": \"" + video.shortDescription + "\"");
   }

   //Tags should be a list of strings
   if (video.tags.Count > 0) {
    jsonVideo.Append(",\"tags\": [");
    string append = "";
    foreach (string tag in video.tags) {
     jsonVideo.Append(append + "\"" + tag + "\"");
     append = ",";
    }
    jsonVideo.Append("]");
   }

   //referenceId
   if (!string.IsNullOrEmpty(video.referenceId)) {
    jsonVideo.Append(",\"referenceId\": \"" + video.referenceId + "\"");
   }

   //longDescription
   if (!string.IsNullOrEmpty(video.longDescription)) {
    jsonVideo.Append(",\"longDescription\": \"" + video.longDescription + "\"");
   }

            if (video.cuePoints.Count > 0) {
                jsonVideo.Append(",\"cuePoints\": " + video.cuePoints.ToJSON());
            }

   //economics
   jsonVideo.Append(",\"economics\": " + video.economics.ToString());

   jsonVideo.Append("}");

   return jsonVideo.ToString();
  }

  #endregion
 }
}

CustomFields:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace BrightcoveSDK
{
    [CollectionDataContract]
    public class CustomFields : Dictionary<string, string>
    { }
}
+1  A: 

JSON dictionaries aren't deserialized as dictionaries, but as arrays of KeyValuePair<T,U>

Change your datamember type to KeyValuePair<string, string>[]

James L
It looks like the problem is that the customFields object being returned doesn't fit into .NET's definition of a Dictionary or KeyValuePair. It sees it as an object with properties called "custom title" and "custom title 2". To be deserialized as a KeyValuePair the structure needs to look like this: {"key" : "some key", "value" : "some value"}. Thanks for your help.
mstiles
A: 

Hey James,

I'm actually working on the exact same issue. We just started using customFields from Brightcove and I'm finding that the KeyValuePair[] type for the datamember does not work. And I fear that if I look in a mirror I'm going to have a keyboard imprint on my forehead.

Could you tell me how you ended up solving this problem?

Thanks! -Nick

NickS
I was able to get it working by creating a class that had the custom fields as properties and setting the customField property on the video class to that class type. Although this creates other issues since you may have different custom fields being returned for any given video. I haven't actually fixed my issue yet but I'm going to attempt to pass in a generic type T for the class that the customField object is. I'm hoping that this will allow me to specify the type when the call is made.
mstiles
I see. I did the same and that does work. Luckily we only have one customField. Searching around yesterday I noticed that the BC API allows you to specify a specific customfield(s) to return. Maybe that way the return set could be somewhat controlled. Thanks!
NickS
+1  A: 

The CollectionDataContract attribute can be used to specify a contract that serializes into a JSON "dictionary", namely an array of key-value pair objects. A sample would look something like this:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace DictionarySample
{
    [CollectionDataContract(Name = "dict", ItemName = "key", KeyName = "key", ValueName = "value")]
    public class DictContract : Dictionary<string, string>
    {
        public DictContract() { }
        public DictContract(SerializationInfo info, StreamingContext context : base(info, context) { }
    }
}
Ben