c# Serialize with JSON.NET inherited private fields
I have an object structure (in external dll) like this:
public class Demo2 { private int count; public Demo2() { count = 2; } } public class MyDemo : Demo2 { private int count; public MyDemo() { count = 3; } } public class Perform { static void Main(string[] args) { MyDemo d = new MyDemo();I need something this: "{count: 3, base: {count:2}}". And Deserialize later
String json = JsonSerializer.SerializeOnce(d); Console.WriteLine(json); /* print: {count: 3} */ } }
Assuming your object structure (in external dll) cannot be modified in any way, you can still create the JSON you require by using a custom JsonConverter
that internally makes use of a custom ContractResolver
to generate a list of public and private fields at each level of the type hierarchy with associated get and set methods:
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)
return 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);
if (property == null)
continue;
var value = jProperty.Value.ToObject(property.PropertyType, serializer);
property.ValueProvider.SetValue(existingValue, value);
}
type = type.BaseType;
jObj = basejObj;
}
return existingValue;
}
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);
}
writer.WriteEndObject();
}
}
public static class JsonExtensions
{
public static JToken ExtractPropertyValue(this JObject obj, string name)
{
if (obj == null)
return null;
var property = obj.Property(name);
if (property == null)
return null;
var value = property.Value;
property.Remove();
property.Value = null;
return value;
}
}
class DeclaredFieldContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static DeclaredFieldContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
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;
return contract;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, MemberSerialization.Fields);
}
}
Then use it as follows:
var demo = new MyDemo();
var json = JsonConvert.SerializeObject(demo, new DeclaredFieldJsonConverter<MyDemo>());
Sample fiddle.
Note that, if there is a field named base
anywhere in the type hierarchy, duplicated JSON property names will be written resulting in potential loss of information when deserializing. You may want to check for this and handle it in some manner.