gpt4 book ai didi

c# - 为旧的 JSON 结构添加向后兼容性支持

转载 作者:行者123 更新时间:2023-12-03 13:54:33 26 4
gpt4 key购买 nike

我为 android 开发了一个应用程序,它将 JSON 文件中的序列化域模型存储到本地存储。现在的问题是,有时我会更改域模型(新功能)并希望可以选择 轻松从本地存储加载 JSON 文件的先前结构。我怎样才能做到这一点?

我想匿名反序列化对象并使用自动映射器,但我想在走这条路之前先听听别人的想法。

如果需要域模型的代码示例(之前和之后),我会提供。谢谢大家。

最佳答案

您如何支持向后兼容性取决于您的“之前”和“之后”模型将有多大不同。
如果您只是要添加新属性,那么这根本不会造成问题;您可以将旧的 JSON 反序列化为新模型,它可以正常工作而不会出错。
如果您用不同的属性替换过时的属性,您可以使用 Making a property deserialize but not serialize with json.net 中描述的技术。将旧属性迁移到新属性。
如果您要进行重大的结构更改,那么您可能希望为每个版本使用不同的类。序列化模型时,请确保 Version属性(或其他一些可靠的标记)被写入 JSON。然后当需要反序列化时,您可以将 JSON 加载到 JToken 中。 , 检查 Version属性,然后为 JToken 中的版本填充适当的模型.如果你愿意,你可以把这个逻辑封装成一个 JsonConverter 类(class)。

让我们来看看一些例子。假设我们正在编写一个应用程序,它保留一些关于人的信息。我们将从最简单的模型开始:Person具有一个人名的单一属性的类。

public class Person  // Version 1
{
public string Name { get; set; }
}
让我们创建一个人的“数据库”(我将在这里使用一个简单的列表)并将其序列化。
List<Person> people = new List<Person>
{
new Person { Name = "Joe Schmoe" }
};
string json = JsonConvert.SerializeObject(people);
Console.WriteLine(json);
这为我们提供了以下 JSON。
[{"Name":"Joe Schmoe"}]
fiddle : https://dotnetfiddle.net/NTOnu2

好的,现在说我们要增强应用程序以跟踪人们的生日。这对于向后兼容性来说不是问题,因为我们只是要添加一个新属性;它不会以任何方式影响现有数据。这是 Person类看起来像新属性:
public class Person  // Version 2
{
public string Name { get; set; }
public DateTime? Birthday { get; set; }
}
为了测试它,我们可以将版本 1 数据反序列化为这个新模型,然后向列表中添加一个新人并将模型序列化回 JSON。 (我还将添加一个格式选项以使 JSON 更易于阅读。)
List<Person> people = JsonConvert.DeserializeObject<List<Person>>(json);
people.Add(new Person { Name = "Jane Doe", Birthday = new DateTime(1988, 10, 6) });
json = JsonConvert.SerializeObject(people, Formatting.Indented);
Console.WriteLine(json);
一切都很好。这是 JSON 现在的样子:
[
{
"Name": "Joe Schmoe",
"Birthday": null
},
{
"Name": "Jane Doe",
"Birthday": "1988-10-06T00:00:00"
}
]
fiddle : https://dotnetfiddle.net/pftGav

好的,现在假设我们已经意识到只需使用一个 Name属性不够强大。如果我们有单独的 FirstName 会更好和 LastName属性代替。这样我们就可以做一些事情,比如按目录顺序(最后一个,第一个)对名字进行排序,并打印非正式的问候语,比如“嗨,乔!”。
幸运的是,我们知道到目前为止已经可靠地输入了数据,名字在姓氏之前,它们之间有一个空格,因此我们有一个可行的升级路径:我们可以拆分 Name属性并从中填充两个新属性。在我们这样做之后,我们要处理 Name过时的属性(property);我们不希望它在 future 写回 JSON。
让我们对模型进行一些更改以实现这些目标。添加两个新的字符串属性后 FirstNameLastName ,我们需要改变旧的 Name属性如下:
  • 使其set方法集FirstNameLastName如上所述的属性;
  • 删除它的 get方法使Name属性不会写入 JSON;
  • 将其设为私有(private),使其不再是 Person 的公共(public)接口(interface)的一部分;
  • 添加 [JsonProperty]属性,以便 Json.Net 仍然可以“看到”它,即使它是私有(private)的。

  • 当然,我们必须更新任何其他使用 Name 的代码。属性改为使用新属性。这是我们的 Person类现在看起来像:
    public class Person  // Version 3
    {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime? Birthday { get; set; }

    // This property is here to support transitioning from Version 2 to Version 3
    [JsonProperty]
    private string Name
    {
    set
    {
    if (value != null)
    {
    string[] parts = value.Trim().Split(' ');
    if (parts.Length > 0) FirstName = parts[0];
    if (parts.Length > 1) LastName = parts[1];
    }
    }
    }
    }
    为了证明一切正常,让我们将版本 2 JSON 加载到此模型中,按姓氏对人员进行排序,然后将其重新序列化为 JSON:
    List<Person> people = JsonConvert.DeserializeObject<List<Person>>(json);
    people = people.OrderBy(p => p.LastName).ThenBy(p => p.FirstName).ToList();
    json = JsonConvert.SerializeObject(people, Formatting.Indented);
    Console.WriteLine(json);
    看起来挺好的!结果如下:
    [
    {
    "FirstName": "Jane",
    "LastName": "Doe",
    "Birthday": "1988-10-06T00:00:00"
    },
    {
    "FirstName": "Joe",
    "LastName": "Schmoe",
    "Birthday": null
    }
    ]
    fiddle : https://dotnetfiddle.net/T8NXMM

    现在是大的。假设我们想要添加一个新功能来跟踪每个人的家庭住址。但关键是,人们可以共享相同的地址,在这种情况下我们不希望出现重复的数据。这需要对我们的数据模型进行重大更改,因为到目前为止它只是一个人员列表。现在我们需要第二个地址列表,我们需要一种将人员与地址联系起来的方法。当然,我们仍然希望支持读取所有旧数据格式。我们应该怎么做?
    首先让我们创建我们需要的新类。我们需要一个 Address类(class)类别:
    public class Address
    {
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    }
    我们可以重复使用相同的 Person类(class);我们需要的唯一更改是添加 AddressId属性将每个人链接到一个地址。
    public class Person
    {
    public int? AddressId { get; set; }
    ...
    }
    最后,我们在根级别需要一个新类来保存人员和地址列表。我们也给它一个 Version属性以防我们将来需要更改数据模型:
    public class RootModel
    {
    public string Version { get { return "4"; } }
    public List<Person> People { get; set; }
    public List<Address> Addresses { get; set; }
    }
    模型就是这样;现在最大的问题是我们如何处理不同的 JSON?在版本 3 及更早版本中,JSON 是一个对象数组。但是对于这个新模型,JSON 将是一个包含两个数组的对象。
    解决方案是使用自定义 JsonConverter 对于新模型。我们可以将 JSON 读入 JToken然后根据我们发现的内容(数组与对象)以不同方式填充新模型。如果我们得到一个对象,我们将检查我们刚刚添加到模型中的新版本号属性。
    这是转换器的代码:
    public class RootModelConverter : JsonConverter
    {
    public override bool CanConvert(Type objectType)
    {
    return objectType == typeof(RootModel);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
    JToken token = JToken.Load(reader);
    RootModel model = new RootModel();
    if (token.Type == JTokenType.Array)
    {
    // we have a Version 3 or earlier model, which is just a list of people.
    model.People = token.ToObject<List<Person>>(serializer);
    model.Addresses = new List<Address>();
    return model;
    }
    else if (token.Type == JTokenType.Object)
    {
    // Check that the version is something we are expecting
    string version = (string)token["Version"];
    if (version == "4")
    {
    // all good, so populate the current model
    serializer.Populate(token.CreateReader(), model);
    return model;
    }
    else
    {
    throw new JsonException("Unexpected version: " + version);
    }
    }
    else
    {
    throw new JsonException("Unexpected token: " + token.Type);
    }
    }

    // This signals that we just want to use the default serialization for writing
    public override bool CanWrite
    {
    get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    throw new NotImplementedException();
    }
    }
    要使用转换器,我们创建一个实例并将其传递给 DeserializeObject像这样的方法:
    RootModelConverter converter = new RootModelConverter();
    RootModel model = JsonConvert.DeserializeObject<RootModel>(json, converter);
    现在我们已经加载了模型,我们可以更新数据以显示 Joe 和 Jane 住在同一地址并将其再次序列化:
    model.Addresses.Add(new Address
    {
    Id = 1,
    Street = "123 Main Street",
    City = "Birmingham",
    State = "AL",
    PostalCode = "35201",
    Country = "USA"
    });

    foreach (var person in model.People)
    {
    person.AddressId = 1;
    }

    json = JsonConvert.SerializeObject(model, Formatting.Indented);
    Console.WriteLine(json);
    这是生成的 JSON:
    {
    "Version": 4,
    "People": [
    {
    "FirstName": "Jane",
    "LastName": "Doe",
    "Birthday": "1988-10-06T00:00:00",
    "AddressId": 1
    },
    {
    "FirstName": "Joe",
    "LastName": "Schmoe",
    "Birthday": null,
    "AddressId": 1
    }
    ],
    "Addresses": [
    {
    "Id": 1,
    "Street": "123 Main Street",
    "City": "Birmingham",
    "State": "AL",
    "PostalCode": "35201",
    "Country": "USA"
    }
    ]
    }
    我们可以通过再次反序列化并转储一些数据来确认转换器也适用于新的第 4 版 JSON 格式:
    model = JsonConvert.DeserializeObject<RootModel>(json, converter);
    foreach (var person in model.People)
    {
    Address addr = model.Addresses.FirstOrDefault(a => a.Id == person.AddressId);
    Console.Write(person.FirstName + " " + person.LastName);
    Console.WriteLine(addr != null ? " lives in " + addr.City + ", " + addr.State : "");
    }
    输出:
    Jane Doe lives in Birmingham, AL
    Joe Schmoe lives in Birmingham, AL
    fiddle : https://dotnetfiddle.net/4lcDvE

    关于c# - 为旧的 JSON 结构添加向后兼容性支持,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53391878/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com