简体   繁体   中英

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM