using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class DeclaredFieldJsonConverter<T> : JsonConverter where T: new()
const string basePropertyName = "base";
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
var jObj = JObject.Load(reader);
existingValue = existingValue ?? new T();
var type = existingValue.GetType();
while (jObj != null && type != null)
var basejObj = jObj.ExtractPropertyValue(basePropertyName) as JObject;
JsonObjectContract contract = (JsonObjectContract)DeclaredFieldContractResolver.Instance.ResolveContract(type);
foreach (var jProperty in jObj.Properties())
var property = contract.Properties.GetClosestMatchProperty(jProperty.Name);
var value = jProperty.Value.ToObject(property.PropertyType, serializer);
property.ValueProvider.SetValue(existingValue, value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
WriteJson(writer, value, value.GetType(), serializer);
void WriteJson(JsonWriter writer, object value, Type type, JsonSerializer serializer)
JsonObjectContract contract = (JsonObjectContract)DeclaredFieldContractResolver.Instance.ResolveContract(type);
writer.WriteStartObject();
foreach (var property in contract.Properties.Where(p => !p.Ignored))
writer.WritePropertyName(property.PropertyName);
serializer.Serialize(writer, property.ValueProvider.GetValue(value));
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(object))
writer.WritePropertyName(basePropertyName);
WriteJson(writer, value, baseType, serializer);
public static class JsonExtensions
public static JToken ExtractPropertyValue(this JObject obj, string name)
var property = obj.Property(name);
var value = property.Value;
class DeclaredFieldContractResolver : DefaultContractResolver
static DeclaredFieldContractResolver instance;
static DeclaredFieldContractResolver() { instance = new DeclaredFieldContractResolver(); }
public static DeclaredFieldContractResolver Instance { get { return instance; } }
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
var fields = objectType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(f => !f.IsNotSerialized);
return fields.Cast<MemberInfo>().ToList();
protected override JsonObjectContract CreateObjectContract(Type objectType)
var contract = base.CreateObjectContract(objectType);
contract.MemberSerialization = MemberSerialization.Fields;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
return base.CreateProperties(type, MemberSerialization.Fields);
bool ShouldBeIgnored = true;
public class MyDemo : Demo2
public static void Test()
Console.WriteLine("Testing default serialization...");
var json = JsonConvert.SerializeObject(demo, new DeclaredFieldJsonConverter<MyDemo>());
var expectedJson = @"{""count"": 3, ""base"": {""count"":2}}";
if (!JToken.DeepEquals(JToken.Parse(expectedJson), JToken.Parse(json)))
throw new InvalidOperationException("!JToken.DeepEquals(JToken.Parse(expectedJson), JToken.Parse(json))");
Console.WriteLine("Default serialization is as expected.");
static void TestDemoRoundTrip()
Console.WriteLine("Testing round-trip with non-default values...");
var json = @"{""count"": 33, ""base"": {""count"":22}}";
var demo = JsonConvert.DeserializeObject<MyDemo>(json, new DeclaredFieldJsonConverter<MyDemo>());
var json2 = JsonConvert.SerializeObject(demo, new DeclaredFieldJsonConverter<MyDemo>());
Console.WriteLine(json2);
if (!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2)))
throw new InvalidOperationException("!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(json2))");
Console.WriteLine(string.Format("{0} successfully round-tripped", demo));
if (JObject.Parse(json2).SelectTokens("$..ShouldBeIgnored").Any())
throw new InvalidOperationException("JObject.Parse(json2).SelectTokens(\"$..ShouldBeIgnored\").Any()");
public static void Main()
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);