【问题标题】:Json.Net: How to ignore null elements in array deserializing a JSONJson.Net:如何忽略数组反序列化 JSON 中的空元素
【发布时间】:2020-11-06 11:30:38
【问题描述】:

我有这个 JSON:

{
    "Variable1": "1",
    "Variable2": "50000",
    "ArrayObject": [null]
}

我有这个存根:

public class Class1
{
  public string Variable1 { get; set; }
  public string Variable2 { get; set; }
  [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
  public List<ArrayObject> ArrayObject { get; set; }
}

public class ArrayObject
{
  public string VariableArray1 { get; set; }
  public string VariableArray2 { get; set; }
}

我想忽略数组中的空元素,最好使用 json 设置或某种转换器。因此,在这种情况下,结果应该是一个空数组或 null。

这是我一直在尝试的代码。

class Program
{
  static void Main(string[] args)
  {
    string json = @"{
      ""Variable1"": ""1"",
      ""Variable2"": ""50000"",
      ""ArrayObject"": [null]
    }";

    var settings = new JsonSerializerSettings()
    {
      ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
      NullValueHandling = NullValueHandling.Ignore,
    };

    Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);

    Console.WriteLine(class1.ArrayObject == null);
    Console.WriteLine(class1.ArrayObject.Count());

    foreach (var item in class1.ArrayObject)
    {
      Console.WriteLine(item.VariableArray1);
      Console.WriteLine(item.VariableArray2);
      Console.WriteLine("#######################");
    }
  }

  public class Class1
  {
    public string Variable1 { get; set; }
    public string Variable2 { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<ArrayObject> ArrayObject { get; set; }
  }

  public class ArrayObject
  {
    public string VariableArray1 { get; set; }
    public string VariableArray2 { get; set; }
  }
}

我认为使用NullValueHandling = NullValueHandling.Ignore 可以让它工作。显然不是。有什么想法吗?

更新:我需要一个全局解决方案,我不想修改项目中的每个视图模型。

【问题讨论】:

  • NullValueHandling = NullValueHandling.Ignore 遇到什么问题?
  • 正如我所说,我想在反序列化时忽略数组中的空元素。
  • NullValueHandling 有助于在序列化期间决定是否应反序列化或忽略具有空值的属性。基于 NullValueHandling 的值,生成的 json 将具有设置为 null 的属性名称,否则 json 将根本没有该属性。所以NullValueHandling在反序列化过程中帮助不大
  • @DCCoder - 这不回答问题,NullValueHandling=NullValueHandling.Ignore 不过滤掉数组项。

标签: c# json json.net


【解决方案1】:

设置 NullValueHandling = NullValueHandling.Ignore 不会在反序列化期间自动过滤 JSON 数组中的空值,因为这样做会导致数组中的剩余项被重新索引,从而使可能具有的任何数组索引无效存储在序列化图中的其他位置。

如果您不关心保留数组索引并希望在反序列化期间从数组中过滤空值,则需要实现custom JsonConverter,如下所示:

public class NullFilteringListConverter<T> : JsonConverter<List<T>>
{
    public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        serializer.Populate(reader, list);
        list.RemoveAll(i => i == null);
        return list;
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => throw new NotImplementedException();
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

并将其应用于您的模型,如下所示:

public class Class1
{
    public string Variable1 { get; set; }
    public string Variable2 { get; set; }
    [JsonConverter(typeof(NullFilteringListConverter<ArrayObject>))]
    public List<ArrayObject> ArrayObject { get; set; }
}

或者,在设置中添加如下:

var settings = new JsonSerializerSettings()
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    NullValueHandling = NullValueHandling.Ignore,
    Converters = { new NullFilteringListConverter<ArrayObject>() }, 
};

注意事项:

  • 由于您在序列化期间没有询问过滤空值,因此我没有实现它,但是通过更改 CanWrite =&gt; true; 并将 WriteJson() 替换为:

     public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => serializer.Serialize(writer, value.Where(i => i != null));
    

演示小提琴herehere

更新

我需要一个全局解决方案。 如果您需要从每个模型中所有可能的 List&lt;T&gt; 对象中自动过滤所有 null 值,那么以下 JsonConverter 将完成这项工作: p>

public class NullFilteringListConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (objectType.IsArray || objectType == typeof(string) || objectType.IsPrimitive)
            return false;
        var itemType = objectType.GetListItemType();
        return itemType != null && (!itemType.IsValueType || Nullable.GetUnderlyingType(itemType) != null);
    }

    object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        serializer.Populate(reader, list);
        list.RemoveAll(i => i == null);
        return list;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        var itemType = objectType.GetListItemType();
        var method = typeof(NullFilteringListConverter).GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
        try
        {
            return method.MakeGenericMethod(new[] { itemType }).Invoke(this, new object[] { reader, objectType, existingValue, serializer });
        }
        catch (Exception ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializerException
            throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
        }
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

public static partial class JsonExtensions
{
    internal static Type GetListItemType(this Type type)
    {
        // Quick reject for performance
        if (type.IsPrimitive || type.IsArray || type == typeof(string))
            return null;
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

并将其添加到设置中,如下所示:

var settings = new JsonSerializerSettings()
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    NullValueHandling = NullValueHandling.Ignore,
    Converters = { new NullFilteringListConverter() },
};

Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);

使用此转换器,不再需要将[JsonConverter(typeof(NullFilteringListConverter&lt;ArrayObject&gt;))] 添加到ArrayObject。请注意,反序列化图中的所有 List&lt;T&gt; 实例可能会在使用这些设置时重新索引!确保您确实希望这是更改其他地方引用的项目索引的副作用索引可能包括数据损坏(不正确的引用)而不是直接的ArgumentOutOfRangeException

演示小提琴#3 here.

【讨论】:

  • 我可以将此转换器添加到 json 设置中,以便它在每个列表上全局工作吗?还是我需要进入每个模型并添加此属性?
  • 您可以为每个相关的T 添加一个NullFilteringListConverter&lt;T&gt;JsonSerializerSettings.Converters,并且将对模型中的所有List&lt;T&gt; 集合进行过滤。如果您需要一个 single 转换器来处理所有可能的List&lt;T&gt; 集合,那么这需要一些额外的工作。如果这是您需要的,请编辑您的问题并添加详细信息。您当前的问题显示 JsonProperty(NullValueHandling = NullValueHandling.Ignore)] 已应用于该属性,因此我认为特定属性的解决方案就足够了。
【解决方案2】:

NullValueHandling.Ignore 不会过滤数组中的空值。但是你可以通过在你的类中添加一个反序列化回调方法来过滤掉空值来轻松地做到这一点:

public class Class1
{
    public string Variable1 { get; set; }
    public string Variable2 { get; set; }
    public List<ArrayObject> ArrayObject { get; set; }

    [OnDeserialized]
    internal void OnDeserialized(StreamingContext context)
    {
        ArrayObject?.RemoveAll(o => o == null);
    }
}

工作演示:https://dotnetfiddle.net/v9yn7j

【讨论】:

  • 我必须把它放在我项目中的每个视图模型中。我需要一个全球解决方案。
  • 我需要一个全球解决方案 - 这是您在问题中遗漏的一个非常重要的细节。然后,您需要使用通用的JsonConverter。请参阅@dbc 的更新答案。
【解决方案3】:

您还可以有一个自定义设置器来过滤掉空值。

private List<ArrayObject> _arrayObject;
public List<ArrayObject> ArrayObject
{
    get => _arrayObject;
    set
    {
        _arrayObject = value.Where(x => x != null).ToList();
    }
}

在这里工作的小提琴https://dotnetfiddle.net/ePp0A2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-31
    • 2012-02-08
    • 2015-10-23
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 1970-01-01
    相关资源
    最近更新 更多