Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
576 views
in Technique[技术] by (71.8m points)

c# - How To Serialize a class that derives from a Dictionary

I am attempting to serialize/deserialize the following class to and from Json using Json.Net:

public class ChildDictionary:Dictionary<Employee, double>
{

    public string Name { get; set; }

}

I have found the information here, here, and here that are related but none of them deal specifically with what the syntax should look like for this case where we derive from a Dictionary.

Employee successfully serializes with Json.Net on its own. It looks like this:

[JsonObject(MemberSerialization.OptIn)]
public class Employee
{

    [JsonProperty]
    public string Name { get; set; }

    [JsonProperty]
    public double Factor { get; set; }

    [JsonProperty]
    public List<ILoadBuilder> LoadBuilders = new List<ILoadBuilder>();

    [JsonConstructor]
    public LoadCause(string name, double factor, List<ILoadBuilder> loadBuilders)
    {
        this.Name = name;
        this.DurationFactor = Factor;
        this.LoadBuilders = loadBuilders;
    }
}

I don't care what the Json looks like in the end as long as I can write and read it without losing data

Any suggestions of what the code to accomplish this should look like? Both a Custom JsonConverter or Attributes are fine solutions.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Because your dictionary has both a complex key and additional properties, you will need to use a custom JsonConverter to serialize and deserialize this class. Below is a converter that should do the job. It handles the serialization in two parts: first it uses reflection to deal with any read-write properties on the class, then it casts the object to a dictionary interface to handle the key-value pairs. The latter are written to the JSON as an array of objects with Key and Value properties so that the complex keys are managed without needing to jump through extra hoops.

public class ComplexDictionaryConverter<K,V> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<K,V>)));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JObject obj = new JObject();
        foreach (PropertyInfo prop in GetReadWriteProperties(value.GetType()))
        {
            object val = prop.GetValue(value);
            obj.Add(prop.Name, val != null ? JToken.FromObject(val, serializer) : new JValue(val));
        }
        JArray array = new JArray();
        foreach (var kvp in (IDictionary<K, V>)value)
        {
            JObject item = new JObject();
            item.Add("Key", JToken.FromObject(kvp.Key, serializer));
            item.Add("Value", kvp.Value != null ? JToken.FromObject(kvp.Value, serializer) : new JValue(kvp.Value));
            array.Add(item);
        }
        obj.Add("KVPs", array);
        obj.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        IDictionary<K, V> dict = (IDictionary<K, V>)Activator.CreateInstance(objectType);
        foreach (PropertyInfo prop in GetReadWriteProperties(objectType))
        {
            JToken token = obj[prop.Name];
            object val = token != null ? token.ToObject(prop.PropertyType, serializer) : null;
            prop.SetValue(dict, val);
        }
        JArray array = (JArray)obj["KVPs"];
        foreach (JObject kvp in array.Children<JObject>())
        {
            K key = kvp["Key"].ToObject<K>(serializer);
            V val = kvp["Value"].ToObject<V>(serializer);
            dict.Add(key, val);
        }
        return dict;
    }

    private IEnumerable<PropertyInfo> GetReadWriteProperties(Type type)
    {
        return type.GetProperties().Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any());
    }
}

To use the converter, you can mark your class with a [JsonConverter] attribute like this (be sure the generic parameters match those of the dictionary your class inherits from):

[JsonConverter(typeof(ComplexDictionaryConverter<Employee, double>))]
public class ChildDictionary : Dictionary<Employee, double>
{
    ...
}

Here is a demo showing a full round-trip:

class Program
{
    static void Main(string[] args)
    {
        ChildDictionary dict = new ChildDictionary();
        dict.Name = "Roster";
        dict.Add(new Employee { Id = 22, Name = "Joe", HireDate = new DateTime(2012, 4, 17) }, 1923.07);
        dict.Add(new Employee { Id = 45, Name = "Fred", HireDate = new DateTime(2010, 8, 22) }, 1415.25);

        string json = JsonConvert.SerializeObject(dict, Formatting.Indented);
        Console.WriteLine(json);

        dict = JsonConvert.DeserializeObject<ChildDictionary>(json);
        Console.WriteLine("Name: " + dict.Name);

        foreach (var kvp in dict)
        {
            Console.WriteLine("Employee Id: " + kvp.Key.Id);
            Console.WriteLine("Employee Name: " + kvp.Key.Name);
            Console.WriteLine("Employee Hire Date: " + kvp.Key.HireDate);
            Console.WriteLine("Amount: " + kvp.Value);
            Console.WriteLine();
        }
    }
}

[JsonConverter(typeof(ComplexDictionaryConverter<Employee, double>))]
public class ChildDictionary : Dictionary<Employee, double>
{
    public string Name { get; set; }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime HireDate { get; set; }
}

Output:

{
  "Name": "Roster",
  "KVPs": [
    {
      "Key": {
        "Id": 22,
        "Name": "Joe",

        "HireDate": "2012-04-17T00:00:00"
      },
      "Value": 1923.07
    },
    {
      "Key": {
        "Id": 45,
        "Name": "Fred",
        "HireDate": "2010-08-22T00:00:00"
      },
      "Value": 1415.25
    }
  ]
}
Name: Roster
Employee Id: 22
Employee Name: Joe
Employee Hire Date: 4/17/2012 12:00:00 AM
Amount: 1923.07

Employee Id: 45
Employee Name: Fred
Employee Hire Date: 8/22/2010 12:00:00 AM
Amount: 1415.25

Fiddle: https://dotnetfiddle.net/fTfoIk


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...