在 C# 中反序列化为 类 的现有实例
Deserialize to existing instance of classes in C#
这可以应用于多个问题,其中列表的项目数量有限,例如城市、货币、语言等的列表。
我正在尝试找到一种方法将 class 序列化为其 Id,然后反序列化回其完整描述。例如,如果我们有这样的城市结构:
public struct City
{
public string Name;
public Country Country;
}
[JsonObject(MemberSerialization.OptIn)]
public class Country
{
public Country(string code, string name) { Code = code; Name = name; }
[JsonProperty]
public string Code;
public string Name;
}
public class Countries
{
public static List<Country> All = new List<Country>()
{
new Country("US", "United States"),
new Country("GB", "United Kingdom"),
new Country("FR", "France"),
new Country("ES", "Spain"),
};
}
我不介意序列化为 {"code":"ES"} 或简单地 "ES" 的国家/地区,但我希望将其反序列化为国家/地区的现有实例Countries.All
我怎么会出现这种行为?
你有两个解决方案:
- 使用枚举和扩展函数,这将简化验证过程,并且枚举有特定的限制
- 使用自定义 JsonConverters 使用 ids/code 转换您的 JSON 数据,这将允许您自定义将它们序列化为 JSON
的方式
我很快就会有例子,我需要在通过之前输入它们。
编辑:示例已完成
两种情况
在这两个示例中,我都使用 JsonConvertAttribute 来设置在序列化或反序列化对象时应使用哪个转换器。此参数告诉 json.net 库在默认情况下序列化 object/parameter 时使用哪个 class/converter。
如果您只需要在特定时刻进行序列化,则必须针对 2 种不同的场景进行选择:
- 序列化 array/list 时
- 将 JsonPropertyAttribute 添加到 属性 并将 ItemConverterType 设置为转换器的类型。
- 序列化其他任何东西时
- 将 JsonConvertAttribute 添加到 属性 并在构造函数中传递 JSON 转换器。
保持国家 object/struct
在我看来,此选项是 2 种解决方案中最灵活和实用的,因为它允许更改需求并减少异常抛出。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApplication
{
public struct City
{
public string Name;
// you could also set a converter for this field specifically if you only need for specific fields but also
// still want it to display as a normal json object when you serialise the object.
// [JsonConverter(typeof(CountryConverter))]
public Country Country;
}
// Setting a json converter attribute allows json.net to understand that an object by default
// will be serialised and deserialised using the specified converter.
[JsonConverter(typeof(CountryConverter))]
public class Country
{
public Country(string code)
{
switch (code)
{
case "US": Name = "United-States"; break;
case "GB": Name = "United Kingdom"; break;
case "FR": Name = "France"; break;
case "ES": Name = "Spain"; break;
case "CA": Name = "Canada"; break;
}
}
public string Code { get; set; }
public string Name { get; set; }
}
public class CountryConverter : JsonConverter<Country>
{
// Assuming that the countries are serialised using the code
public override Country ReadJson(JsonReader reader, Type objectType, Country existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if(reader.Value == null && reader.ValueType == typeof(string))
{
return null;
}
string code = (string) reader.Value;
code = code.ToUpperInvariant(); // Because reducing error points is usually a good thing
return new Country(code);
}
public override void WriteJson(JsonWriter writer, Country value, JsonSerializer serializer)
{
//Writes the code as the value for the object
writer.WriteValue(value.Code);
}
}
}
将国家/地区更改为枚举
使用枚举可能很棒,因为它允许您拥有一组不变的值,而不必一遍又一遍地创建新对象。但这会使您的转换逻辑稍微复杂一些。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApplication
{
public struct City
{
public string Name;
public Country Country;
}
// Using the full name as it makes it easier to work with and also because you'd need a json converter
// if you want a string and not the number/index of the country when you serialise the data
[JsonConverter(typeof(CountryConverter))]
public enum Country
{
UnitedStates,
UnitedKingdom,
France,
Spain,
Canada
}
// this class only exists for you to add extentions to this enums.
// In short extentions are a type of methods added to other classes that act
// as if they were part of outher classes. usually this means that the first parameter
// is prefixed by this.
public static class CountryExtentions
{
public static string GetCode(this Country country)
{
switch (country)
{
case Country.UnitedStates: return "US";
case Country.UnitedKingdom: return "GB";
case Country.France: return "FR";
case Country.Spain: return "SP";
case Country.Canada: return "CA";
default: throw new InvalidOperationException($"This country has no code {country.ToString()}");
}
}
}
public class CountryConverter : JsonConverter<Country>
{
// Assuming that the countries are serialised using the code
public override Country ReadJson(JsonReader reader, Type objectType, Country existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// make sure you can convert the thing into a string
if (reader.Value == null && reader.ValueType == typeof(string))
{
throw new InvalidOperationException($"The data type passed {reader.ValueType.Name} isn't convertible. The data type musts be a string.");
}
// get the value
string code = (string)reader.Value;
code = code.ToUpperInvariant(); // Because reducing error points is usually a good thing
// cycle through the enum values to compare them to the code
foreach (Country country in Enum.GetValues(typeof(Country)))
{
// if the code matches
if (country.GetCode() == code)
{
// return the country enum
return country;
}
}
// if no match is found, the code is invlalid
throw new InvalidCastException("The provided code could not be converted.");
}
public override void WriteJson(JsonWriter writer, Country value, JsonSerializer serializer)
{
//Writes the code as the value for the object
writer.WriteValue(value.GetCode());
}
}
}
我建议像这样使用 JsonConverter
:
public class CountryConverter : JsonConverter
{
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return typeof(Country) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
var code = obj.GetValue("code", StringComparison.OrdinalIgnoreCase)?.Value<string>();
if (code != null)
{
return Countries.All.FirstOrDefault(c => string.Equals(c.Code, code, StringComparison.OrdinalIgnoreCase));
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
它不负责序列化对象,只负责反序列化。为了反序列化它们,它读取 "code" 字段,然后 returns 来自 Countries.All
列表的第一个匹配国家。最好(更有效)使用字典代替它。
要使用它,只需像这样装饰您的国家 class:
[JsonConverter(typeof(CountryConverter))]
public class Country
这可以应用于多个问题,其中列表的项目数量有限,例如城市、货币、语言等的列表。
我正在尝试找到一种方法将 class 序列化为其 Id,然后反序列化回其完整描述。例如,如果我们有这样的城市结构:
public struct City
{
public string Name;
public Country Country;
}
[JsonObject(MemberSerialization.OptIn)]
public class Country
{
public Country(string code, string name) { Code = code; Name = name; }
[JsonProperty]
public string Code;
public string Name;
}
public class Countries
{
public static List<Country> All = new List<Country>()
{
new Country("US", "United States"),
new Country("GB", "United Kingdom"),
new Country("FR", "France"),
new Country("ES", "Spain"),
};
}
我不介意序列化为 {"code":"ES"} 或简单地 "ES" 的国家/地区,但我希望将其反序列化为国家/地区的现有实例Countries.All
我怎么会出现这种行为?
你有两个解决方案:
- 使用枚举和扩展函数,这将简化验证过程,并且枚举有特定的限制
- 使用自定义 JsonConverters 使用 ids/code 转换您的 JSON 数据,这将允许您自定义将它们序列化为 JSON 的方式
我很快就会有例子,我需要在通过之前输入它们。 编辑:示例已完成
两种情况
在这两个示例中,我都使用 JsonConvertAttribute 来设置在序列化或反序列化对象时应使用哪个转换器。此参数告诉 json.net 库在默认情况下序列化 object/parameter 时使用哪个 class/converter。
如果您只需要在特定时刻进行序列化,则必须针对 2 种不同的场景进行选择:
- 序列化 array/list 时
- 将 JsonPropertyAttribute 添加到 属性 并将 ItemConverterType 设置为转换器的类型。
- 序列化其他任何东西时
- 将 JsonConvertAttribute 添加到 属性 并在构造函数中传递 JSON 转换器。
保持国家 object/struct
在我看来,此选项是 2 种解决方案中最灵活和实用的,因为它允许更改需求并减少异常抛出。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApplication
{
public struct City
{
public string Name;
// you could also set a converter for this field specifically if you only need for specific fields but also
// still want it to display as a normal json object when you serialise the object.
// [JsonConverter(typeof(CountryConverter))]
public Country Country;
}
// Setting a json converter attribute allows json.net to understand that an object by default
// will be serialised and deserialised using the specified converter.
[JsonConverter(typeof(CountryConverter))]
public class Country
{
public Country(string code)
{
switch (code)
{
case "US": Name = "United-States"; break;
case "GB": Name = "United Kingdom"; break;
case "FR": Name = "France"; break;
case "ES": Name = "Spain"; break;
case "CA": Name = "Canada"; break;
}
}
public string Code { get; set; }
public string Name { get; set; }
}
public class CountryConverter : JsonConverter<Country>
{
// Assuming that the countries are serialised using the code
public override Country ReadJson(JsonReader reader, Type objectType, Country existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if(reader.Value == null && reader.ValueType == typeof(string))
{
return null;
}
string code = (string) reader.Value;
code = code.ToUpperInvariant(); // Because reducing error points is usually a good thing
return new Country(code);
}
public override void WriteJson(JsonWriter writer, Country value, JsonSerializer serializer)
{
//Writes the code as the value for the object
writer.WriteValue(value.Code);
}
}
}
将国家/地区更改为枚举
使用枚举可能很棒,因为它允许您拥有一组不变的值,而不必一遍又一遍地创建新对象。但这会使您的转换逻辑稍微复杂一些。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApplication
{
public struct City
{
public string Name;
public Country Country;
}
// Using the full name as it makes it easier to work with and also because you'd need a json converter
// if you want a string and not the number/index of the country when you serialise the data
[JsonConverter(typeof(CountryConverter))]
public enum Country
{
UnitedStates,
UnitedKingdom,
France,
Spain,
Canada
}
// this class only exists for you to add extentions to this enums.
// In short extentions are a type of methods added to other classes that act
// as if they were part of outher classes. usually this means that the first parameter
// is prefixed by this.
public static class CountryExtentions
{
public static string GetCode(this Country country)
{
switch (country)
{
case Country.UnitedStates: return "US";
case Country.UnitedKingdom: return "GB";
case Country.France: return "FR";
case Country.Spain: return "SP";
case Country.Canada: return "CA";
default: throw new InvalidOperationException($"This country has no code {country.ToString()}");
}
}
}
public class CountryConverter : JsonConverter<Country>
{
// Assuming that the countries are serialised using the code
public override Country ReadJson(JsonReader reader, Type objectType, Country existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// make sure you can convert the thing into a string
if (reader.Value == null && reader.ValueType == typeof(string))
{
throw new InvalidOperationException($"The data type passed {reader.ValueType.Name} isn't convertible. The data type musts be a string.");
}
// get the value
string code = (string)reader.Value;
code = code.ToUpperInvariant(); // Because reducing error points is usually a good thing
// cycle through the enum values to compare them to the code
foreach (Country country in Enum.GetValues(typeof(Country)))
{
// if the code matches
if (country.GetCode() == code)
{
// return the country enum
return country;
}
}
// if no match is found, the code is invlalid
throw new InvalidCastException("The provided code could not be converted.");
}
public override void WriteJson(JsonWriter writer, Country value, JsonSerializer serializer)
{
//Writes the code as the value for the object
writer.WriteValue(value.GetCode());
}
}
}
我建议像这样使用 JsonConverter
:
public class CountryConverter : JsonConverter
{
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return typeof(Country) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
var code = obj.GetValue("code", StringComparison.OrdinalIgnoreCase)?.Value<string>();
if (code != null)
{
return Countries.All.FirstOrDefault(c => string.Equals(c.Code, code, StringComparison.OrdinalIgnoreCase));
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
它不负责序列化对象,只负责反序列化。为了反序列化它们,它读取 "code" 字段,然后 returns 来自 Countries.All
列表的第一个匹配国家。最好(更有效)使用字典代替它。
要使用它,只需像这样装饰您的国家 class:
[JsonConverter(typeof(CountryConverter))]
public class Country