C # Serialization with Inherited JSON.NET Private Fields

I have an object structure (in an 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(); String json = JsonSerializer.SerializeOnce<MyDemo>(d); Console.WriteLine(json); /* print: {count: 3} */ } } 

I need something like this: "{count: 3, base: {count: 2}}". And deserialize later

+1
source share
1 answer

Assuming your object structure (in an external dll) cannot be modified in any way, you can still create the required JSON using custom JsonConverter , which internally uses custom ContractResolver to create a list of public and private fields at each level of the type hierarchy with corresponding 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>()); 

Fiddle example.

Please note that if the type hierarchy has a field named base , duplicate JSON property names will be written, which will lead to potential loss of information during deserialization. You can check this out and process it in some way.

+2
source

Source: https://habr.com/ru/post/1275493/


All Articles