在版本/格式之间迁移序列化 Json.NET 文档的策略

D. *_*gan 5 c# versioning json.net

我正在使用 Json.Net 来序列化一些应用程序数据。当然,应用规范略有变化,我们需要重构一些业务对象数据。将以前序列化的数据迁移到我们的新数据格式有哪些可行的策略?

例如,假设我们最初有一个业务对象,如:

public class Owner
{
    public string Name {get;set;} 
}
public class LeaseInstrument
{
    public ObservableCollection<Owner> OriginalLessees {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

我们使用 Json.Net 将 LeaseInstrument 的一个实例序列化为一个文件。现在,我们将业务对象更改为如下所示:

public class Owner
{
   public string Name {get;set;}
}
public class LeaseOwner
{
  public Owner Owner { get;set;}
  public string DocumentName {get;set;}
}
public class LeaseInstrument
{
    public ObservableCollection<LeaseOwner> OriginalLessees {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

我曾考虑为 LeaseInstrument 编写自定义 JsonConverter,但从未命中 ReadJson 方法……而是在反序列化器到达该点之前抛出异常:

Additional information: Type specified in JSON
'System.Collections.ObjectModel.ObservableCollection`1[[BreakoutLib.BO.Owner,
BreakoutLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]],
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
is not compatible with 'System.Collections.ObjectModel.ObservableCollection`1[[BreakoutLib.BO.LeaseOwner, BreakoutLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Path 'Is.$values[8].OriginalLessors.$type', line 3142, position 120.
Run Code Online (Sandbox Code Playgroud)

我的意思是,不是开玩笑,Json.Net,这就是我在反序列化这些对象时尝试运行 JsonConverter 的原因,所以我可以手动处理序列化类型与编译类型不匹配的事实!!

对于它的价值,这里是我们正在使用的 JsonSerializerSettings:

var settings = new JsonSerializerSettings
    {
      PreserveReferencesHandling = PreserveReferencesHandling.Objects,
      ContractResolver = new WritablePropertiesOnlyResolver(),
      TypeNameHandling = TypeNameHandling.All,
      ObjectCreationHandling = ObjectCreationHandling.Reuse
    };
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 5

您有以下问题:

  1. 您使用 进行序列化TypeNameHandling.All。此设置序列化集合和对象的类型信息。我不建议这样做。相反,我建议使用TypeNameHandling.Objects然后让反序列化系统选择集合类型。

    话虽这么说,为了处理现有的 JSON,如果它与可调整大小的集合不兼容,您可以调整IgnoreArrayTypeConvertermake Json.NET 忽略 $type :

    public class IgnoreCollectionTypeConverter : JsonConverter
    {
        public IgnoreCollectionTypeConverter() { }
    
        public IgnoreCollectionTypeConverter(Type ItemConverterType) 
        { 
            this.ItemConverterType = ItemConverterType; 
        }
    
        public Type ItemConverterType { get; set; }
    
        public override bool CanConvert(Type objectType)
        {
            // TODO: test with read-only collections.
            return objectType.GetCollectItemTypes().Count() == 1 && !objectType.IsDictionary() && !objectType.IsArray;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (!CanConvert(objectType))
                throw new JsonSerializationException(string.Format("Invalid type \"{0}\"", objectType));
            if (reader.TokenType == JsonToken.Null)
                return null;
            var token = JToken.Load(reader);
            var itemConverter = (ItemConverterType == null ? null : (JsonConverter)Activator.CreateInstance(ItemConverterType, true));
            if (itemConverter != null)
                serializer.Converters.Add(itemConverter);
    
            try
            {
                return ToCollection(token, objectType, existingValue, serializer);
            }
            finally
            {
                if (itemConverter != null)
                    serializer.Converters.RemoveLast(itemConverter);
            }
        }
    
        private static object ToCollection(JToken token, Type collectionType, object existingValue, JsonSerializer serializer)
        {
            if (token == null || token.Type == JTokenType.Null)
                return null;
            else if (token.Type == JTokenType.Array)
            {
                // Here we assume that existingValue already is of the correct type, if non-null.
                existingValue = serializer.DefaultCreate<object>(collectionType, existingValue);
                token.PopulateObject(existingValue, serializer);
                return existingValue;
            }
            else if (token.Type == JTokenType.Object)
            {
                var values = token["$values"];
                if (values == null)
                    return null;
                return ToCollection(values, collectionType, existingValue, serializer);
            }
            else
            {
                throw new JsonSerializationException("Unknown token type: " + token.ToString());
            }
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 您需要将您的升级OwnerLeaseOwner.

    JsonConverter为此,您可以编写一个将 JSON 的相关部分加载到 a 中JObject,然后检查该对象是否类似于旧数据模型或新数据模型中的对象。如果 JSON 看起来很旧,请根据需要使用Linq to JSON映射字段。如果 JSON 对象看起来是新的,您可以用它填充您的LeaseOwner对象。

    由于您正在设置PreserveReferencesHandling = PreserveReferencesHandling.Objects转换器,因此需要"$ref"手动处理属性:

    public class OwnerToLeaseOwnerConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(LeaseOwner).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var item = JObject.Load(reader);
            if (item["$ref"] != null)
            {
                var previous = serializer.ReferenceResolver.ResolveReference(serializer, (string)item["$ref"]);
                if (previous is LeaseOwner)
                    return previous;
                else if (previous is Owner)
                {
                    var leaseOwner = serializer.DefaultCreate<LeaseOwner>(objectType, existingValue);
                    leaseOwner.Owner = (Owner)previous;
                    return leaseOwner;
                }
                else
                {
                    throw new JsonSerializationException("Invalid type of previous object: " + previous);
                }
            }
            else
            {
                var leaseOwner = serializer.DefaultCreate<LeaseOwner>(objectType, existingValue);
                if (item["Name"] != null)
                {
                    // Convert from Owner to LeaseOwner.  If $id is present, this stores the reference mapping in the reference table for us.
                    leaseOwner.Owner = item.ToObject<Owner>(serializer);
                }
                else
                {
                    // PopulateObject.  If $id is present, this stores the reference mapping in the reference table for us.
                    item.PopulateObject(leaseOwner, serializer);
                }
                return leaseOwner;
            }
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

这些使用扩展:

public static class JsonExtensions
{
    public static T DefaultCreate<T>(this JsonSerializer serializer, Type objectType, object existingValue)
    {
        if (serializer == null)
            throw new ArgumentNullException();
        if (existingValue is T)
            return (T)existingValue;
        return (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
    }

    public static void PopulateObject(this JToken obj, object target, JsonSerializer serializer)
    {
        if (target == null)
            throw new NullReferenceException();
        if (obj == null)
            return;
        using (var reader = obj.CreateReader())
            serializer.Populate(reader, target);
    }
}

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> GetCollectItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }

    public static bool IsDictionary(this Type type)
    {
        if (typeof(IDictionary).IsAssignableFrom(type))
            return true;

        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            {
                return true;
            }
        }
        return false;
    }
}

public static class ListExtensions
{
    public static bool RemoveLast<T>(this IList<T> list, T item)
    {
        if (list == null)
            throw new ArgumentNullException();
        var comparer = EqualityComparer<T>.Default;
        for (int i = list.Count - 1; i >= 0; i--)
        {
            if (comparer.Equals(list[i], item))
            {
                list.RemoveAt(i);
                return true;
            }
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用 将转换器直接应用到您的数据模型JsonConverterAttribute,如下所示:

public class LeaseInstrument
{
    [JsonConverter(typeof(IgnoreCollectionTypeConverter), typeof(OwnerToLeaseOwnerConverter))]
    public ObservableCollection<LeaseOwner> OriginalLessees { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

如果您不想在数据模型中依赖 Json.NET,则可以在自定义合同解析器中执行此操作:

public class WritablePropertiesOnlyResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var result = base.CreateProperty(member, memberSerialization);
        if (typeof(LeaseInstrument).IsAssignableFrom(result.DeclaringType) && typeof(ICollection<LeaseOwner>).IsAssignableFrom(result.PropertyType))
        {
            var converter = new IgnoreCollectionTypeConverter { ItemConverterType = typeof(OwnerToLeaseOwnerConverter) };
            result.Converter = result.Converter ?? converter;
            result.MemberConverter = result.MemberConverter ?? converter;
        }
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,您可能希望缓存自定义合同解析器以获得最佳性能。