c# - 用于数组和集合的自定义 Newtonsoft JsonConverter 以进行进一步操作

标签 c# .net-core json.net .net-core-2.2

我想对字符串数组(或 IEnumerable)使用自定义 JsonConverter 并对数组进行一些操作(实际上删除所有为空或空格的字符串)。

但是我已经陷入了 ReadJson 方法中,不知道如何正确获取 string[]。

我为简单的字符串做了一个自定义转换器,在那里我检查了 JsonToken.String。但是数组有 StartArray 和 EndArray ...

任何已经反/序列化他们的自定义字符串数组并且可以帮助我的人?

更多细节:

我想要实现的是对模型绑定(bind)进行集中或可选的字符串修剪(因此我不必在每个 Controller 中都这样做),并且模型验证检查重复项会将“字符串”和“字符串”检测为重复项。

我正在尝试以 JsonConverters 的身份这样做(挖掘模型绑定(bind)日志输出、.net 核心文档、.net 核心 github 代码让我想到了 json 转换器是最好的)。

集中使用将在启动 Json 选项中配置:

  public void ConfigureServices(IServiceCollection services)
  {
    services
      .AddMvc()
      .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
      .AddJsonOptions
      (
        options => 
        {
          options.SerializerSettings.Converters.Add(new TrimmedStringConverter());
          options.SerializerSettings.Converters.Add(new CleanStringArrayConverter());
        }
      );
  }

在每个模型的基础上使用它看起来像
  public class RequestModel
  {

    [JsonConverter(typeof(TrimmedStringConverter))]
    public string MyValue { get; set; }

    [JsonConverter(typeof(CleanStringArrayConverter))]
    public string[] Entries { get; set; }

  }

This question提供了在模型绑定(bind)时自动修剪字符串的转换器。我只是加了些盐。
  public class TrimmedStringConverter : JsonConverter
  {

    public bool EmptyStringsAsNull { get; }

    public TrimmedStringConverter()
    {
      EmptyStringsAsNull = true;
    }

    public TrimmedStringConverter(bool emptyStringsAsNull)
    {
      EmptyStringsAsNull = emptyStringsAsNull;
    }

    public override bool CanConvert(Type objectType)
    {
      return objectType == typeof(string);
    }

    private string CleanString(string str)
    {
      if (str == null) return null;

      str = str.Trim();

      if (str.Length == 0 && EmptyStringsAsNull) return null;

      return str;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {

      if (reader.TokenType == JsonToken.String)
      {
        //if (reader.Value != null)
          return CleanString(reader.Value as string);
      }

      return reader.Value;

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
      var text = (string)value;
      if (text == null)
        writer.WriteNull();
      else
        writer.WriteValue(CleanString(text));
    }

  }

现在这让我的模型在 string[] 中有空字符串或空值。我现在尝试在第二个转换器(或执行上述相同操作但用于字符串数组、集合的转换器)中自动删除。

我只是无法弄清楚如何使用读取器和序列化器正确处理数组序列化/反序列化。

这就是我得到的程度(感谢 Silvermind)。字符串数组的第二个转换器。

首先,我也设法在 CleanStringArrayConverter 中使用了全局注册的 TrimmedStringConverter(检查附加的注释代码)。只要 TrimmedStringConverter 在全局范围内使用并且 CleanStringArrayConverter 在每个模型的基础上,这就会起作用。全局使用两者会导致无限循环和 SERVER CRASHES 以及 Access Violation 异常。

所以我把它改成了这个版本,两者都可以同时在全局范围内注册。

不幸的是,它只适用于数组。

你们中的某个人可能会找到这个代码,使用它并可以分享改进吗?
  public class CleanStringArrayConverter : JsonConverter
  {

    public bool TrimStrings { get; }
    public bool EmptyStringsAsNull { get; }
    public bool RemoveWhitespace { get; }
    public bool RemoveNulls { get; }
    public bool RemoveEmpty { get; }


    public CleanStringArrayConverter()
    {
      TrimStrings = true;
      EmptyStringsAsNull = true;
      RemoveWhitespace = true;
      RemoveNulls = true;
      RemoveEmpty = true;
    }

    public CleanStringArrayConverter(bool trimStrings = true, bool emptyStringsAsNull = true, bool removeWhitespace = true, bool removeEmpty = true, bool removeNulls = true)
    {
      TrimStrings = trimStrings;
      EmptyStringsAsNull = emptyStringsAsNull;
      RemoveWhitespace = removeWhitespace;
      RemoveNulls = removeNulls;
      RemoveEmpty = removeEmpty;
    }

    private string CleanString(string str)
    {
      if (str == null) return null;

      if (TrimStrings) str = str.Trim();

      if (str.Length == 0 && EmptyStringsAsNull) return null;

      return str;
    }

    private string[] CleanStringCollection(IEnumerable<string> strings)
    {
      if (strings == null) return null;

      return strings
        .Select(s => CleanString(s))
        .Where
        (
          s =>
          {
            if (s == null) return !RemoveNulls;
            else if (s.Equals(string.Empty)) return !RemoveEmpty;
            else if (string.IsNullOrWhiteSpace(s)) return !RemoveWhitespace;
            else return true;
          }
        )
        .ToArray();
    }

    public override bool CanConvert(Type objectType)
    {
      return objectType.IsArray && objectType.GetElementType() == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {

      string[] arr = null;            // use null as default value
      //string[] arr = new string[];  // use empty array as default value

      // deserialze the array
      if (reader.TokenType != JsonToken.Null)
      {
        if (reader.TokenType == JsonToken.StartArray)
        {
          // this one respects other registered converters (e.g. the TrimmedStringConverter)
          // but causes server crashes when used globally due to endless loops
          //arr = serializer.Deserialize<string[]>(reader);

          // this doesn't respect others!!!
          JToken token = JToken.Load(reader);
          arr = token.ToObject<string[]>();
        }
      }

      // clean up the array
      if (arr != null) arr = CleanStringCollection(arr);

      return arr;

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {

      string[] arr = (string[])value;

      if (value == null)
      {
        writer.WriteNull();
        return;
      }

      arr = CleanStringCollection(arr);

      // endless loops and server crashes!!!
      //serializer.Serialize(writer, arr);


      writer.WriteStartArray();
      string v;
      foreach(string s in arr)
      {
        v = CleanString(s);
        if (v == null)
            writer.WriteNull();
        else
            writer.WriteValue(v);
      }

      writer.WriteEndArray();
    }

  }

最佳答案

这基本上是相同的想法:

internal sealed class TrimmedStringCollectionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsArray && objectType.GetElementType() == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (existingValue is null)
        {
             // Returning empty array???
             return new string[0];
        }

        var array = (string[])existingValue;
        return array.Where(s => !String.IsNullOrEmpty(s)).ToArray();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(value);
    }
}

也许您可能想对写入部分做同样的事情。

关于c# - 用于数组和集合的自定义 Newtonsoft JsonConverter 以进行进一步操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56889847/

相关文章:

c# - 我可以用 c# 解密在 php 中用 PASSWORD_BCRYPT 加密的密码吗?

c# - 在 MacOS 上对来自 .NET Core 的 HttpClient 调用进行身份验证

azure - 为什么即使在将我的 AppSettings JSON 文件从配置生成器中注释掉之后,它们仍然被包含在内?

c# - 如何使用 Json.Net 和带有子类型的 Xamarin 反序列化 JSON?

.net - JSON 日期序列化和时区

javascript - 解决来自 JSON 对象的循环引用

c# - MahApps 和属性网格

c# - 获取 Nuget 包丢失错误

c# - ActionResult 的不同结果

c# - 更新后无法构建 .NET Core 项目