我想将二维数组反序列化为 .net 对象的集合。原因是,数组语法对于我的用户来说更容易在输入文件中使用。所以我只想将数组的索引映射到目标类型的特定属性。
例如跟:
[
["John", "Smith", "23"],
["Paula", "Martin", "54]
]
我会得到一个人的两个实例:
public class Person {
public string First {get;set;}
public string Last {get;set;}
public string Age {get;set;}
}
其中内部数组的索引 0 映射到 First
,索引 1 映射到 Last
,索引 2 映射到 Age
;
有没有办法扩展 Json.NET 以便我可以在反序列化期间进行映射,以便隐藏实现细节?我一直在玩自定义JsonConverter
但我没有找到太多有关如何使用它的信息。
编辑:具体来说,我不确定JsonConverter
是否是正确的使用方式,并且我无法弄清楚如何实现CanConvert
以及如何使用传递给ReadJson
方法的参数。
JsonConverter
执行此操作。 用于此目的的简单转换器是:
public class PersonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Person);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var array = JArray.Load(reader);
var person = (existingValue as Person ?? new Person());
person.First = (string)array.ElementAtOrDefault(0);
person.Last = (string)array.ElementAtOrDefault(1);
person.Age = (string)array.ElementAtOrDefault(2);
return person;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var person = (Person)value;
serializer.Serialize(writer, new[] { person.First, person.Last, person.Age });
}
}
如果特定属性具有非基元类型,则可以使用 JToken.ToObject<T>(JsonSerializer)
将它们反序列化为所需的类型:
person.First = array.ElementAtOrDefault(0)?.ToObject<string>(serializer);
然后,您可以将其应用于您的类:
[JsonConverter(typeof(PersonConverter))]
public class Person
{
public string First { get; set; }
public string Last { get; set; }
public string Age { get; set; }
}
或在设置中使用它:
var settings = new JsonSerializerSettings { Converters = new [] { new PersonConverter() } };
var list = JsonConvert.DeserializeObject<List<Person>>(json, settings);
对于通用转换器,下面是使用属性关联属性的实现:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class JsonArrayIndexAttribute : Attribute
{
public JsonArrayIndexAttribute(int index)
{
Index = index;
}
public int Index { get; }
}
public class JsonArrayConverter<T> : JsonConverter<T>
{
public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.StartArray:
var members = GetIndexedMembers(objectType);
var arr = JArray.ReadFrom(reader);
var fromArray = hasExistingValue ? existingValue! : Activator.CreateInstance<T>()!;
foreach (var (member, value) in members.Zip(arr))
{
member.setter?.Invoke(fromArray, Convert.ChangeType(value, member.type));
}
return fromArray;
case JsonToken.StartObject:
var fromObject = hasExistingValue ? existingValue! : Activator.CreateInstance<T>()!;
serializer.Populate(reader, fromObject);
return fromObject;
case JsonToken.Null:
return default;
case JsonToken.Undefined:
return hasExistingValue ? existingValue : default;
default:
throw new JsonSerializationException($"Unexpected TokenType: {reader.TokenType}");
}
}
public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
var members = GetIndexedMembers(value.GetType());
writer.WriteStartArray();
foreach (var member in members)
{
writer.WriteValue(member.getter?.Invoke(value));
}
writer.WriteEndArray();
}
private IEnumerable<(Type? type, Func<object, object?>? getter, Action<object, object?>? setter)> GetIndexedMembers(Type type)
{
var indexedMembers =
(from member in type.GetMembers()
let index = (JsonArrayIndexAttribute?)member.GetCustomAttribute(typeof(JsonArrayIndexAttribute))
where index != null
select (member, index.Index))
.ToLookup(x => x.Index, x => x.member);
return
(from i in Enumerable.Range(0, indexedMembers.Max(x => x.Key) + 1)
from m in indexedMembers[i].TakeLast(1).DefaultIfEmpty()
select CreateAccessors(m));
(Type, Func<object, object?>?, Action<object, object?>?) CreateAccessors(MemberInfo m) => m switch
{
PropertyInfo p => (p.PropertyType, obj => p.GetValue(obj), (obj, value) => p.SetValue(obj, value)),
FieldInfo f => (f.FieldType, obj => f.GetValue(obj), (obj, value) => f.SetValue(obj, value)),
_ => default,
};
}
}
然后在您的情况下使用它:
[JsonConverter(typeof(JsonArrayConverter<Person>))]
public class Person
{
[JsonArrayIndex(0)]
public string First { get; set; } = default!;
[JsonArrayIndex(1)]
public string Last { get; set; } = default!;
[JsonArrayIndex(2)]
public int Age { get; set; }
}
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(List<Person>)) return true;
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
List<Person> persons = new List<Person>();
JArray personsArray = (JArray)serializer.Deserialize(reader);
foreach (var personArray in personsArray.Children<JArray>())
{
persons.Add(new Person() {
First = personArray[0].Value<string>(),
Last = personArray[1].Value<string>(),
Age = personArray[2].Value<string>()
});
}
return persons;
}