【问题标题】:Deserialization of JSON from previous data model with Custom Converters fails when deserializing collections of Interfaces反序列化接口集合时,使用自定义转换器从以前的数据模型反序列化 JSON 失败
【发布时间】:2016-02-18 20:00:33
【问题描述】:

我负责维护一个游戏系统,在该系统中,用户将 JSON 序列化 POCO 保存在本地缓存中,以保存 Character 等状态。

最新版本的代码改变了这些序列化对象的数据模型。特别是,创建了一个新界面。当将旧的字符副本反序列化到新代码中时,这会产生问题。我正在尝试使用自定义转换器解决这些问题,但遇到了麻烦。

旧的序列化版本:

public class Character{
  public Skill Parent {get;set;}
  public Dictionary<string,Skill} Skills {get;set;}
}

public class Skill {
//normal stuff here.
}

新版本:

public class Character{
  [JsonProperty, JsonConverter(typeof(ConcreteTypeConverter<Dictionary<string,Skill>>))]
  public Dictionary<string,ISkill} Skills {get;set;}
}

public class Skill:ISkill {
//normal stuff here.
}

public interface ISkill{
//stuff that all skill-like things have here
}

我进一步定义了一个自定义转换器类(已阅读 thisthis

但我仍然遇到反序列化集合的麻烦。

public class Extensions 
{

//a lot of serializer extensions here including the Deserialize method
 private static readonly CustomSerializationBinder Binder = new CustomSerializationBinder();
        private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            TypeNameHandling = TypeNameHandling.Objects,
            TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
            Binder = Binder,
        };
}


 public class CustomSerializationBinder : DefaultSerializationBinder
    {


        public override Type BindToType(string assemblyName, string typeName)
        {

             return base.BindToType(assemblyName, typeName);


        }
    }

    public class ConcreteTypeConverter<T> : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer,value); // serialization isnt't the problem.
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (typeof (T)==typeof(Dictionary<string,Skill>))
            {
                var retVal = new object();
                if (reader.TokenType == JsonToken.StartObject)
                {
                    T instance = (T)serializer.Deserialize(reader, typeof(T));  //crashes here
                    retVal = new List<T>() { instance };
                    return retVal;
                }
            }

            return serializer.Deserialize<T>(reader);
        }

        public override bool CanConvert(Type objectType)
        {
            return true; // kind of a hack 
        }
    }

所以我有一个旧的 Dictionary&lt;string,Skill&gt;,我无法在我能看到的任何代码路径中将其转换为Dictionary&lt;string,ISkill&gt;。我应该如何解决这个问题?

【问题讨论】:

  • 看看stackoverflow.com/questions/33321698/…。这提供了一个通用转换器,该转换器将通过使用$type(如果存在)来确定要为ISkill 接口反序列化哪个具体的Skill 类,如果不存在,则选择最佳属性匹配。
  • 我去看看。我已经在使用$type 来解决一些问题(以膨胀为代价),所以也许这是一个很好的前进方向。我担心的问题是陈旧的 JSON 将有一个 $type,即 Skill,而不是 ISkill——以及如何处理 $type,即 Dictionary&lt;string,Skill&gt;
  • 我不是 100% 确定我理解您的问题。您的旧 JSON(从 SkillISkill 重构之前)是否包含字典本身的 $type 信息?
  • 是的。在这种情况下,旧版 JSON 看起来像 $type: "System.Collections.Generic.Dictionary'2[[System.String, mscorlib],[DataModel.Skill, PROJECT_NAME]]"

标签: c# serialization json.net


【解决方案1】:

由于您的旧 JSON 已经包含包括字典对象在内的所有对象的类型信息,您需要做的是剥离字典的类型信息并允许反序列化的字典类型由代码控制,而不是 JSON。

以下转换器应该可以完成这项工作:

public class IgnoreDictionaryTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.GetDictionaryKeyValueTypes().Count() == 1;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        var obj = JObject.Load(reader);
        obj.Remove("$type");
        using (var subReader = obj.CreateReader())
        {
            serializer.Populate(subReader, existingValue);
        }
        return existingValue;
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class TypeExtensions
{
    /// <summary>
    /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type[]> GetDictionaryKeyValueTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            {
                yield return intType.GetGenericArguments();
            }
        }
    }
}

然后您可以将其添加到设置中,或将其应用于相关的字典属性:

public class Character
{
    [JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
    public IDictionary<string, ISkill> Skills { get; set; }
}

对于未来,您可能还希望禁用发送字典的类型信息,因为字典是集合,并且集合类型最好由代码指定,而不是 JSON:

public class Character
{
    [JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
    [JsonProperty(TypeNameHandling = TypeNameHandling.None)]
    public IDictionary<string, ISkill> Skills { get; set; }
}

【讨论】:

  • 这个,用一个自定义的 SerializationBinder,成功了。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
相关资源
最近更新 更多