using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Specialized;
using System.Web.Routing;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Web.SessionState;
public class OptionalType
public string setting1 { get; set; }
public class OptionalSettingsAttribute : System.Attribute
public OptionalSettingsAttribute()
[JsonConverter(typeof(StringEnumConverter))]
[OptionalSettingsAttribute]
[OptionalSettingsAttribute]
[JsonConverter(typeof(ConfigurationConverter))]
public class Configuration
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
public MyEnumTypes Type { get; set; }
public OptionalType TypeAdditionalData { get; set; }
[JsonProperty(PropertyName = "value")]
public int Value { get; set; }
class ConfigurationConverter : JsonConverter
const string typeName = "type";
public override bool CanConvert(Type objectType)
return typeof(Configuration).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
var obj = JObject.Load(reader);
var type = obj.RemoveProperty(typeName);
using (var subReader = obj.CreateReader())
serializer.Populate(subReader, config);
config.Type = type.ToObject<MyEnumTypes>(serializer);
var dictionary = type.ToObject<Dictionary<MyEnumTypes, OptionalType>>(serializer);
if (dictionary.Count > 0)
config.Type = dictionary.Keys.First();
config.TypeAdditionalData = dictionary.Values.First();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var config = (Configuration)value;
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(config.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties
.Where(p => p.Writable && (p.ShouldSerialize == null || p.ShouldSerialize(config)) && !p.Ignored))
if (property.UnderlyingName == "Type" || property.UnderlyingName == "TypeAdditionalData")
var propertyValue = property.ValueProvider.GetValue(config);
if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, propertyValue);
writer.WritePropertyName(typeName);
if (config.Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null)
serializer.Serialize(writer, config.Type);
var dictionary = new Dictionary<MyEnumTypes, OptionalType>
{ config.Type, config.TypeAdditionalData },
serializer.Serialize(writer, dictionary);
public static class EnumExtensions
public static TAttribute GetCustomAttributeOfEnum<TAttribute>(this Enum value)
where TAttribute : System.Attribute
var type = value.GetType();
var memInfo = type.GetMember(value.ToString());
return memInfo[0].GetCustomAttribute<TAttribute>();
public static class JsonExtensions
public static JToken RemoveProperty(this JObject obj, string name)
var property = obj.Property(name);
var value = property.Value;
private static void Test(Configuration test, JsonSerializerSettings settings)
var json = JsonConvert.SerializeObject(test, Formatting.Indented, settings);
Console.WriteLine("Serialized configuration:");
var testBack = JsonConvert.DeserializeObject<Configuration>(json, settings);
Console.WriteLine("Re-serialized configuration:");
var json2 = JsonConvert.SerializeObject(testBack, Formatting.Indented, settings);
Console.WriteLine(json2);
if (test.Type != testBack.Type)
throw new InvalidOperationException();
if (test.Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() != null)
if (test.TypeAdditionalData != testBack.TypeAdditionalData && !JToken.DeepEquals(JToken.FromObject(test.TypeAdditionalData), JToken.FromObject(testBack.TypeAdditionalData)))
throw new InvalidOperationException();
if (test.Name != testBack.Name || test.Value != testBack.Value)
throw new InvalidOperationException();
Console.WriteLine("Configuration serialized and de-serialized successfully.");
public static void Main()
var test1 = new Configuration { Name = "simple", Value = 101, Type = MyEnumTypes.Simple1, TypeAdditionalData = null };
var test2 = new Configuration { Name = "complex", Value = 202, Type = MyEnumTypes.Optional1, TypeAdditionalData = new OptionalType { setting1 = "optional1" } };
var test3 = new Configuration { Name = "cpmplex", Value = 202, Type = MyEnumTypes.Optional2, TypeAdditionalData = null };