【问题标题】:JSON deserialization - Map array indices to properties with JSON.NETJSON 反序列化 - 使用 JSON.NET 将数组索引映射到属性
【发布时间】:2016-08-13 22:26:49
【问题描述】:

我想将二维数组反序列化为 .net 对象的集合。原因是,我的用户在输入文件中使用数组语法会更容易。所以我只想将数组的索引映射到我的目标类型的特定属性。

例如与:

[
     ["John", "Smith", "23"],
     ["Paula", "Martin", "54]
]

我会得到一个 Person 的两个实例:

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 方法的参数。

【问题讨论】:

  • 看起来像我会尝试做的事情 - 但我建议最后,你最终会使用标准 json。每个“人”的字段名必须重复,但价格不菲。

标签: c# arrays json json.net


【解决方案1】:

您可以使用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);

【讨论】:

  • 这似乎是在正确的轨道上,但它似乎只处理单个 Person 数组,而不是 Person 数组的数组。我将标记为确认 JsonConverter 可以做到的答案。请参阅我提出的实现的答案。如果您发现任何问题,请告诉我。
【解决方案2】:

对于通用转换器,这里有一个使用属性来关联属性的实现:

[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; }
}

【讨论】:

    【解决方案3】:
        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;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多