I'm working on a .NET application for my company to interact with the Nutshell CRM. They have documentation provided for their JSON API here. I'm slowly building out all the classes in my application, but I've run into the issue of only needing to update one field on a call, but having the application include every field that I have on that class.
So for a condensed example of the editLead method, where I'm only modifying the customFields:
Nutshell documentation states that all fields are optional. My classes are set up as the following, where my custom fields in Nutshell are Division, Product, Country:
public class editLead
{
public Customfields customFields { get; set; }
}
public class Customfields
{
public string Division { get; set; }
public string Product { get; set; }
public string Country { get; set; }
}
EDIT (adding more code):
[DataContract(Name = "params")]
public class EditLeadParams
{
public string leadId { get; set; }
public editLead lead { get; set; }
public string rev { get; set; }
}
I'm using RestSharp to make the following call:
var editleadclient = new RestClient();
Method editleadMethod = new Method();
editleadMethod = Method.POST;
var editleadrequest = new RestRequest(editleadMethod);
editleadrequest.RequestFormat = DataFormat.Json;
editleadclient.BaseUrl = new Uri(apiuri);
editleadrequest.Credentials = new NetworkCredential(login, apikey);
leadJSON.EditLeadParams lead1 = new leadJSON.EditLeadParams()
{
leadId = foundlead[0],
lead = new leadJSON.editLead()
{
customFields = new leadJSON.Customfields()
{
Division = "AMERICAS",
}
},
rev = foundlead[1],
};
leadJSON.EditLeadRequest editreq = new leadJSON.EditLeadRequest()
{
#params = lead1,
method = "editLead",
};
editleadrequest.AddBody(editreq);
IRestResponse editResponse = editleadclient.Execute(editleadrequest);
If I only want to update the Division, it will use the following JSON {"customFields":{"Division":"AMERICAS","Product":null,"Country":null}}, and overwrite the Product and Country fields and make them blank. However, if I comment out the Product and Country, in the Customfields definition, it will update the Division and leave the Product and Country alone.
Is there another way to define these classes so that I can have it all defined, but only update what needs to be?
Declaration:
//JsonSerializer.cs
public static class JsonSerializer
{
public static string Serialize(object target, bool ignoreNulls = true)
{
var javaScriptSerializer = new JavaScriptSerializer();
if (ignoreNulls)
{
javaScriptSerializer.RegisterConverters(new[]
{
new NullExclusionConverter(target)
});
}
return javaScriptSerializer.Serialize(target);
}
}
//NullExclusionConverter.cs
public class NullExclusionConverter : JavaScriptConverter
{
private readonly Type _type;
public NullExclusionConverter(object target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
this._type = target.GetType();
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new[] { this._type };
}
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var result = new Dictionary<string, object>();
if (obj == null)
{
return result;
}
var properties = obj.GetType().GetProperties();
foreach (var propertyInfo in properties)
{
//Use propertyInfo.Name to exclude a specific property name
if (propertyInfo.GetValue(obj, null) == null)
{
continue;
}
result.Add(propertyInfo.Name, propertyInfo.GetValue(obj, null));
}
return result;
}
}
Usage:
string jsonString = JsonSerializer.Serialize(objectToSerialize);
Add a reference to System.Web.Extensions
I am leaving my initial answer because it does return only non-null properties as a Json string. However here is your answer when using RestSharp.
On your request add:
editleadrequest.JsonSerializer.Options = new SerializerOptions()
{
SkipNullProperties = true
};
Related
I'd like an extension method to create an object based on another but keep only the primitive properties. This object will be dumped into a log file in JSON format for logging.
Based on the classes shown below, in this sample, the created object should keep only these properties :
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
I am using .NET Framework 4.7
How can I do this?
// To use like this
var order = new Order();
var forLog = order.RemovePrimitives();
// Sample of classes
public class Order
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public List<Item> Items { get; set; }
public Address Address { get; set; }
}
public class Item{}
public class Address{}
public static class Extensions
{
public static string RemovePrimitives(this object obj)
{
// I need to create an anonymous, named 'TheNewObjectHere' object but only with primitives
// I will dump the object to push to a log file. I need only primitives
return JsonConvert.SerializeObject(TheNewObjectHere, Formatting.Indented);
}
}
Thanks
try this
public static class Extensions
{
public static string RemovePrimitives(this object obj)
{
var jsonObj = JObject.FromObject(obj);
var propToRemove = jsonObj.Properties().Where(i => !i.Value.GetType().ToString()
.Contains("JValue")).ToList();
foreach (var prop in propToRemove) prop.Remove();
return jsonObj.ToString();
}
}
You can use reflection to get primitive properties and then use JObject to build a JSON object dynamically.
public static readonly Type[] AdditionalPrimities = new[] { typeof(decimal), typeof(string) };
public static string RemovePrimitives<T>(this T obj)
{
var jObj = new JObject();
var props = GetPrimitiveProperties(obj);
foreach (var item in props)
{
var value = item.GetValue(obj);
if (value != null)
{
jObj.Add(item.Name, new JValue(value));
}
}
return jObj.ToString(Newtonsoft.Json.Formatting.Indented);
}
public static PropertyInfo[] GetPrimitiveProperties<T>()
{
var properties = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(r => r.PropertyType.IsPrimitive || (r.PropertyType.IsGenericType && Nullable.GetUnderlyingType(r.PropertyType) != null) || AdditionalPrimities.Contains(r.PropertyType))
.Select(r => r)
.ToArray();
return properties;
}
public static void Main()
{
var order = new Order { FirstName = "abc", LastName = "cde", Address = new Address(), Age2 = 3, Age = 1 };
var final = order.RemovePrimitives();
Console.WriteLine(final);
}
Fiddle
I have an Azure Server-less Function that serves to take in a JSON payload and work on the records contained. The function works perfectly well to do what is intended except it shouldn't matter the wrapper node name. For example:
{
"Wrapper": [{
"Field1": "Apple",
"Field2": "Peach",
"Field3": "########5",
"Field4": "Kiwi",
}]
}
Should be processed the same way as:
{
"OtherWrapperName": [{
"Column1": "Apple",
"Something": "Peach",
"SomethingElse": "Banana",
"Field4": "Kiwi"
}]
}
Right now it seems to expect the top level node to be called "Wrapper". Here is my attempt at this (some code has been redacted as it was unnecessary for this example):
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
string InputData = await req.Content.ReadAsStringAsync();
var inputData = JsonConvert.DeserializeObject<ItemsPayload>(InputData);
var propertiesLookup = new Dictionary<string, ItemUpdate>();
var propertiesRequest = new PropertySearchRequest { Registry = new List<RequestPropertySearch>() };
int recordCounter = 0;
foreach (var item in inputData.Wrapper)
{
foreach (var kvp in item.Where(property => property.Value.StartsWith("#!!!#")))
{
propertiesLookup[recordCounter.ToString() + "|" + kvp.Value] = new ItemUpdate
{
Properties = item,
UpdateKey = kvp.Key
};
propertiesRequest.Registry.Add(new RequestPropertySearch
{
Token = kvp.Value
});
recordCounter++;
}
}
var intermediateRequest = JsonConvert.SerializeObject(propertiesRequest, Formatting.Indented);
HttpResponseMessage response = MakeRequest(serviceUrl, intermediateRequest, securityHeaderName, securityHeaderValue);
var responseBodyAsText = response.Content.ReadAsStringAsync();
var intermediateData = JsonConvert.DeserializeObject<PropertySearchResponse>(responseBodyAsText.Result);
recordCounter = 0;
foreach (var item in intermediateData.Registry)
{
if (item.Value != null)
{
var itemToUpdate = propertiesLookup[recordCounter.ToString() + "|" + item.Token];
itemToUpdate.Properties[itemToUpdate.UpdateKey] = item.Value;
if (directive.ToLower() == "s")
{
itemToUpdate.Properties[$"#{itemToUpdate.UpdateKey}"] = item.Token;
}
// recordCounter++;
}
recordCounter++;
}
var result = JsonConvert.SerializeObject(inputData, Formatting.Indented);
//return req.CreateResponse(HttpStatusCode.OK, "");
return new HttpResponseMessage()
{
Content = new StringContent(result, System.Text.Encoding.UTF8, "application/json")
};
}
Models:
public class ItemsPayload
{
//public string Directive { get; set; }
public List<Dictionary<string, string>> Wrapper { get; set; }
}
public class PropertySearchRequest
{
public List<RequestPropertySearch> Registry { get; set; }
}
public class RequestPropertySearch
{
public string Token { get; set; }
}
public class PropertySearchResponse
{
public List<ResponsePropertySearch> Registry { get; set; }
}
public class ResponsePropertySearch
{
public string Token { get; set; }
public string Value { get; set; }
public string ProcessId { get; set; }
public string Code { get; set; }
public string Remote { get; set; }
public string Message { get; set; }
}
public class ItemUpdate
{
public Dictionary<string, string> Properties { get; set; }
public string UpdateKey { get; set; }
}
I think the ItemsPayload class property "Wrapper" is causing this as if you change that to something else and rename the node in the JSON it works fine, but I want it to be independent of the name of the top level node. Any thoughts?
You could create a simple JsonConverter for your ItemsPayload to handle the varying wrapper name.
public class ItemsPayloadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ItemsPayload);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
ItemsPayload payload = new ItemsPayload();
// Get the first property of the outer JSON object regardless of its name
// and populate the payload from it
JProperty wrapper = obj.Properties().FirstOrDefault();
if (wrapper != null)
{
payload.Wrapper = wrapper.Value.ToObject<List<Dictionary<string, string>>>(serializer);
}
return payload;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, just annotate your ItemsPayload class with a [JsonConverter] attribute like this and it should work with no other changes to your code:
[JsonConverter(typeof(ItemsPayloadConverter))]
public class ItemsPayload
{
public List<Dictionary<string, string>> Wrapper { get; set; }
}
Fiddle: https://dotnetfiddle.net/9q4tgW
I ran into some trouble trying to convert a class to a BSON document.
I have Custom1 and Custom2 which should behave a little different. How do I create a custom serializer which "unfold" the KeyValuePair so it generates the expected result (See below)? You can see the code example below together with the expected result.
Moreover, I'm using Mongo BSON library for serializing the object.
public class UserData
{
public UserData()
{
Id = 100;
Name = "Superuser";
Custom1 = new KeyValuePair<string, double>("HelloWorld1", 1);
Custom2 = new KeyValuePair<string, double>("HelloWorld2", 2);
}
public int Id { get; set; }
public string Name { get; set; }
public KeyValuePair<string, double> Custom1 { get; set; }
public KeyValuePair<string, double> Custom2 { get; set; }
}
Execute test code:
var userdata = new UserData();
var doc = userdata.ToBsonDocument();
Current result:
{
"Id": 100,
"Name": "Superuser",
"Custom1": {
"Key": "HelloWorld1",
"Value": 1
},
"Custom2": {
"Key": "HelloWorld2",
"Value": 2
}
}
Expected result:
{
"Id": 100,
"Name": "Superuser",
"HelloWorld1": 1,
"HelloWorld2": 2
}
For such a complex serialization case you have to implement custom IBsonSerializer converter for your class.
Here is the working example:
public class UserDataSerializer : SerializerBase<UserData>
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, UserData value)
{
context.Writer.WriteStartDocument();
context.Writer.WriteName("Id");
context.Writer.WriteInt32(value.Id);
context.Writer.WriteName("Name");
context.Writer.WriteString(value.Name);
WriteKeyValue(context.Writer, value.Custom1);
WriteKeyValue(context.Writer, value.Custom2);
context.Writer.WriteEndDocument();
}
private void WriteKeyValue(IBsonWriter writer, KeyValuePair<string, double> kv)
{
writer.WriteName(kv.Key);
writer.WriteDouble(kv.Value);
}
public override UserData Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
//TODO: implement data deserialization using context.Reader
throw new NotImplementedException();
}
}
To make it work you also need to register our UserDataSerializer somehow. IBsonSerializationProvider is the easiest way to achieve that.
public class UserDataSerializationProvider : IBsonSerializationProvider
{
public IBsonSerializer GetSerializer(Type type)
{
if (type == typeof(UserData)) return new UserDataSerializer();
return null;
}
}
And finally we can use it.
//register our serialization provider
BsonSerializer.RegisterSerializationProvider(new UserDataSerializationProvider());
var userdata = new UserData();
var doc = userdata.ToBsonDocument();
Here is the result:
{ "Id" : 100, "Name" : "Superuser", "HelloWorld1" : 1.0, "HelloWorld2" : 2.0 }
You may also want to implement UserDataSerializer.Deserialize() method in order to provide the backward conversion. It can be done in the same way using context.Reader.
More information about custom serialization process can be found here.
If you are using the driver 2.0 or higher, you could try to make your class into a DynamicObject, like this:
public class UserData : DynamicObject
{
public UserData()
{
Id = 100;
Name = "Superuser";
Custom1 = new KeyValuePair<string, double>("HelloWorld1", 1);
Custom2 = new KeyValuePair<string, double>("HelloWorld2", 2);
}
public id Id { get; set; }
public string Name { get; set; }
public KeyValuePair<string, double> Custom1 { get; set; }
public KeyValuePair<string, double> Custom2 { get; set; }
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string name = binder.Name;
result = null;
if(name.Equals(Custom1.Key))
result = Custom1.Value;
else if(name.Equals(Custom2.Key))
result = Custom2.Value;
return result != null;
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
string name = binder.Name;
if(name.Equals(Custom1.Key))
Custom1.Value = value;
else if(name.Equals(Custom2.Key))
Custom2.Value = value;
return name.Equals(Custom1.Key) ||
name.Equals(Custom2.Key);
}
}
According to the release documentation, you should then be able to do this:
var userdata = new UserData();
var doc = ((dynamic) userdata).ToBsonDocument();
And get the expected format.
Sample class:
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
Default deserializer:
var myObject = JsonConvert.DeserializeObject<ClassA>(str);
Create the same object for two different inputs
{"Id":5}
or
{"Id":5,"SomeString":null,"SomeInt":null}
How can I track properties that were missing during deserialization process and preserve the same behavior? Is there a way to override some of JSON.net serializer methods (e.g. DefaultContractResolver class methods) to achive this. For example:
List<string> missingProps;
var myObject = JsonConvert.DeserializeObject<ClassA>(str, settings, missingProps);
For the first input list should contains the names of the missing properties ("SomeString", "SomeInt") and for second input it should be empty. Deserialized object remains the same.
1. JSON has a property which is missing in your class
Using property JsonSerializerSettings.MissingMemberHandling you can say whether missing properties are handled as errors.
Than you can install the Error delegate which will register errors.
This will detect if there is some "garbage" property in JSON string.
public class ClassA
{
public int Id { get; set; }
public string SomeString { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{'Id':5, 'FooBar': 42 }";
var myObject = JsonConvert.DeserializeObject<ClassA>(str
, new JsonSerializerSettings
{
Error = OnError,
MissingMemberHandling = MissingMemberHandling.Error
});
Console.ReadKey();
}
private static void OnError(object sender, ErrorEventArgs args)
{
Console.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
}
2. Your class has a property which is missing in JSON
Option 1:
Make it a required property:
public class ClassB
{
public int Id { get; set; }
[JsonProperty(Required = Required.Always)]
public string SomeString { get; set; }
}
Option 2:
Use some "special" value as a default value and check afterwards.
public class ClassB
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
const string str = "{ 'Id':5 }";
var myObject = JsonConvert.DeserializeObject<ClassB>(str
, new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Populate
});
if (myObject.SomeString == "NOTSET")
{
Console.WriteLine("no value provided for property SomeString");
}
Console.ReadKey();
}
}
Option 3:
Another good idea would be to encapsulate this check iside the class istself. Create a Verify() method as shown below and call it after deserialization.
public class ClassC
{
public int Id { get; set; }
[DefaultValue("NOTSET")]
public string SomeString { get; set; }
public int? SomeInt { get; set; }
public void Verify()
{
if (SomeInt == null ) throw new JsonSerializationException("SomeInt not set!");
if (SomeString == "NOTSET") throw new JsonSerializationException("SomeString not set!");
}
}
Another way to find null/undefined tokens during De-serialization is to write a custom JsonConverter , Here is an example of custom converter which can report both omitted tokens (e.g. "{ 'Id':5 }") and null tokens (e.g {"Id":5,"SomeString":null,"SomeInt":null})
public class NullReportConverter : JsonConverter
{
private readonly List<PropertyInfo> _nullproperties=new List<PropertyInfo>();
public bool ReportDefinedNullTokens { get; set; }
public IEnumerable<PropertyInfo> NullProperties
{
get { return _nullproperties; }
}
public void Clear()
{
_nullproperties.Clear();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
existingValue = existingValue ?? Activator.CreateInstance(objectType, true);
var jObject = JObject.Load(reader);
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var property in properties)
{
var jToken = jObject[property.Name];
if (jToken == null)
{
_nullproperties.Add(property);
continue;
}
var value = jToken.ToObject(property.PropertyType);
if(ReportDefinedNullTokens && value ==null)
_nullproperties.Add(property);
property.SetValue(existingValue, value, null);
}
return existingValue;
}
//NOTE: we can omit writer part if we only want to use the converter for deserializing
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
var properties =
objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
writer.WriteStartObject();
foreach (var property in properties)
{
var propertyValue = property.GetValue(value, null);
writer.WritePropertyName(property.Name);
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndObject();
}
}
Note: we can omit the Writer part if we don't need to use it for serializing objects.
Usage Example:
class Foo
{
public int Id { get; set; }
public string SomeString { get; set; }
public int? SomeInt { get; set; }
}
class Program
{
static void Main(string[] args)
{
var nullConverter=new NullReportConverter();
Console.WriteLine("Pass 1");
var obj0 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5, \"Id\":5}", nullConverter);
foreach(var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
Console.WriteLine("Pass2");
var obj1 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}" , nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
nullConverter.Clear();
nullConverter.ReportDefinedNullTokens = true;
Console.WriteLine("Pass3");
var obj2 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}", nullConverter);
foreach (var p in nullConverter.NullProperties)
Console.WriteLine(p);
}
}
I got this problem, but defaultValue was not solution due to POCO object. I think this is simpler approach than NullReportConverter.
There are three unit tests. Root is class that encapsulate whole json. Key is type of the Property. Hope this helps someone.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
namespace SomeNamespace {
[TestClass]
public class NullParseJsonTest {
[TestMethod]
public void TestMethod1()
{
string slice = "{Key:{guid:\"asdf\"}}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNotNull(result.OptionalKey.Value);
Assert.AreEqual("asdf", result.OptionalKey.Value.Guid);
}
[TestMethod]
public void TestMethod2()
{
string slice = "{Key:null}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsTrue(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
[TestMethod]
public void TestMethod3()
{
string slice = "{}";
var result = JsonConvert.DeserializeObject<Root>(slice);
Assert.IsFalse(result.OptionalKey.IsSet);
Assert.IsNull(result.OptionalKey.Value);
}
}
class Root {
public Key Key {
get {
return OptionalKey.Value;
}
set {
OptionalKey.Value = value;
OptionalKey.IsSet = true; // This does the trick, it is never called by JSON.NET if attribute missing
}
}
[JsonIgnore]
public Optional<Key> OptionalKey = new Optional<Key> { IsSet = false };
};
class Key {
public string Guid { get; set; }
}
class Optional<T> {
public T Value { get; set; }
public bool IsSet { get; set; }
}
}
Ok, so I have this basic class setup...
public class Location
{
public string Name{ get; set; }
private LocationList _LocationList = new LocationList();
public LocationList Locations{ get{ return _LocationList; } }
}
public class LocationList : List<Location>{}
public class ViewModel
{
private LocationList _LocationList = new LocationList();
public LocationList Locations{ get{ return _LocationList; } }
}
which I want to use with the Newtonsoft JSON serializer. However, the serializer doesn't insert the items into the existing collection behind the read-only property accessor, but rather tries to assign an entirely new List to the property, which of course it can't since there isn't a setter.
Now I could just switch to this...
public class Location
{
public string Name{ get; set; }
public LocationList Locations{ get; set; }
}
public class LocationList : List<Location>{}
public class ViewModel
{
public LocationList RootLocations{ get; set; }
}
But now the list property isn't read-only and can be set to null. Even if we make the setter private, JSON deserialization can still set it to null.
What I want is a way to tell the serializer 'Take the items you have in your list and insert them into this already-existing list' rather than saying 'Replace my list with yours altogether'.
So can this be done, or am I going to have to write my own JSON serialization converter and plug that in?
M
I don't know about JSON.NET, but if you use JavaScriptSerializer you can provide a custom serializer, but still use the inbuilt parsing / formatting etc:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Web.Script.Serialization;
class Program
{
static void Main(string[] args)
{
JavaScriptSerializer ser = new JavaScriptSerializer();
ser.RegisterConverters(new[] { new ViewModelConverter() });
var model = new ViewModel { Locations = { new Location { Name = "abc",
Locations = { new Location { Name = "def"}}} } };
var json = ser.Serialize(model);
var clone = (ViewModel)ser.Deserialize(json, typeof(ViewModel));
}
}
public class ViewModelConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(Location), typeof(ViewModel) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
if (obj is ViewModel)
{
return new Dictionary<string, object> { { "locations", ((ViewModel)obj).Locations } };
}
if (obj is Location)
{
return new Dictionary<string, object> {
{"name", ((Location)obj).Name},
{ "locations", ((Location)obj).Locations }
};
}
throw new NotSupportedException();
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (type == typeof(ViewModel))
{
var model = new ViewModel();
ReadLocations(model.Locations, dictionary, serializer);
return model;
}
if (type == typeof(Location))
{
var loc = new Location();
if (dictionary.ContainsKey("name"))
{
loc.Name = (string)dictionary["name"];
}
ReadLocations(loc.Locations, dictionary, serializer);
return loc;
}
throw new NotSupportedException();
}
static void ReadLocations(LocationList locations, IDictionary<string, object> dictionary, JavaScriptSerializer serializer)
{
if (dictionary.ContainsKey("locations"))
{
foreach (object item in (IList)dictionary["locations"])
{
locations.Add((Location)serializer.ConvertToType<Location>(item));
}
}
}
}
public class Location
{
public string Name { get; set; }
private LocationList _LocationList = new LocationList();
public LocationList Locations { get { return _LocationList; } }
}
public class LocationList : List<Location> { }
public class ViewModel
{
private LocationList _LocationList = new LocationList();
public LocationList Locations { get { return _LocationList; } }
}