如何使用 Json.Net 将所有 null 序列化为空字符串

本文关键字:序列化 null 字符串 何使用 Json Net | 更新日期: 2023-09-27 18:31:29

我想将所有空值从字符串和可为空类型序列化为空字符串。 我研究了使用自定义JsonConverter但具有 null 的属性不会传递到转换器中。我考虑过使用合约解析器和值提供程序,但它不允许我将int?的值设置为空字符串。似乎最有希望的是使用覆盖 WriteNull 方法的自定义JsonWriter,但是当我在转换器的 WriteJson 方法中实例化它时,它不会写出任何内容。

public class NullJsonWriter : JsonTextWriter
{
    public NullJsonWriter(TextWriter writer) : base(writer)
    {
    }
    public override void WriteNull()
    {
        base.WriteValue(string.Empty);
    }
}
public class GamesJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(IEnumerable<IGame>).IsAssignableFrom(objectType);
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer = new NullJsonWriter(new StringWriter(new StringBuilder()));
        writer.WriteStartObject();
        if (typeof (IEnumerable).IsAssignableFrom(value.GetType()))
        {
            var list = (IEnumerable<IGame>)value;
            if (!list.Any())
            {
                writer.WriteEndObject();
                return;
            }
            var properties = list.First().GetType().GetProperties();
            PropertyInfo gameId = null;
            foreach (var prop in properties)
            {
                if (prop.Name == "GameId")
                {
                    gameId = prop;
                    break;
                }
            }
            for (int i = 0; i < list.Count(); i++)
            {
                writer.WritePropertyName(string.Format("{0}", gameId.GetValue(list.ElementAt(i))));
                serializer.Serialize(writer, list.ElementAt(i));
            }
        }
        writer.WriteEndObject();
    }

我可以在编写器的StringBuilder中看到 JSON 文本,但它实际上并没有写到我的网页上。

如何使用 Json.Net 将所有 null 序列化为空字符串

我不确定我是否理解为什么要写出一个空字符串来代替所有空值的原因,即使是那些可能代表Nullable<int>或其他对象的空值。 该策略的问题在于,它使 JSON 在反序列化过程中更难处理:如果消费者期待一个Nullable<int>并且他们得到一个字符串,则会导致错误、混乱,并且本质上迫使消费代码使用特殊的转换器或弱类型对象(例如 JObjectdynamic)来绕过不匹配。 但是,让我们暂时把它放在一边,说你有你的理由。

您绝对可以通过子类化 JsonTextWriter 类并重写 WriteNull 方法来编写空字符串来执行您想要的操作。 诀窍是您必须实例化自己的JsonSerializer而不是使用 JsonConvert.SerializeObject ,因为后者没有任何接受JsonWriter的重载。 这是一个简短的概念验证,表明这个想法是可行的:

class Program
{
    static void Main(string[] args)
    {
        // Set up a dummy object with some data. The object also has a bunch
        // of properties that are never set, so they have null values by default.
        Foo foo = new Foo
        {
            String = "test",
            Int = 123,
            Decimal = 3.14M,
            Object = new Bar { Name = "obj1" },
            Array = new Bar[]
            {
                new Bar { Name = "obj2" }
            }
        };
        // The serializer writes to the custom NullJsonWriter, which
        // writes to the StringWriter, which writes to the StringBuilder.
        // We could also use a StreamWriter in place of the StringWriter
        // if we wanted to write to a file or web response or some other stream.
        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        using (NullJsonWriter njw = new NullJsonWriter(sw))
        {
            JsonSerializer ser = new JsonSerializer();
            ser.Formatting = Formatting.Indented;
            ser.Serialize(njw, foo);
        }
        // Get the JSON result from the StringBuilder and write it to the console.
        Console.WriteLine(sb.ToString());
    }
}
class Foo
{
    public string String { get; set; }
    public string NullString { get; set; }
    public int? Int { get; set; }
    public int? NullInt { get; set; }
    public decimal? Decimal { get; set; }
    public decimal? NullDecimal { get; set; }
    public Bar Object { get; set; }
    public Bar NullObject { get; set; }
    public Bar[] Array { get; set; }
    public Bar[] NullArray { get; set; }
}
class Bar
{
    public string Name { get; set; }
}
public class NullJsonWriter : JsonTextWriter
{
    public NullJsonWriter(TextWriter writer) : base(writer)
    {
    }
    public override void WriteNull()
    {
        base.WriteValue(string.Empty);
    }
}

这是输出:

{
  "String": "test",
  "NullString": "",
  "Int": 123,
  "NullInt": "",
  "Decimal": 3.14,
  "NullDecimal": "",
  "Object": {
    "Name": "obj1"
  },
  "NullObject": "",
  "Array": [
    {
      "Name": "obj2"
    }
  ],
  "NullArray": ""
}

小提琴:https://dotnetfiddle.net/QpfG8D

现在让我们谈谈为什么这种方法在您的转换器中没有按预期工作。 当 JSON.Net 调用转换器时,它已经实例化了一个JsonWriter,该将传递到 WriteJson 方法的 writer 参数中。 您所做的不是写入该实例,而是用新的NullJsonWriter实例覆盖此变量。 但请记住,替换引用变量的内容不会替换该变量引用的原始实例。 您已成功写入转换器中的新编写器实例,但您正在写入的所有输出都将进入您在 WriteJson 方法开始时实例化的StringBuilder。 由于您永远不会从StringBuilder中获取 JSON 结果并对其进行某些操作,因此它只是在方法结束时被丢弃。 同时,Json.Net 继续使用其原始JsonWriter实例,您没有在转换器中写入任何内容。 因此,这就是为什么您在最终输出中看不到自定义 JSON 的原因。

有几种方法可以修复代码。 一种方法是在转换器中实例化NullJsonWriter时使用新变量,而不是劫持writer变量。 然后,在 WriteJson 方法结束时,从StringBuilder获取 JSON,并使用原始编写器上的WriteRawValue方法将其写入原始编写器。

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    StringBuilder sb = new StringBuilder();
    using (var innerWriter = new NullJsonWriter(new StringWriter(sb))
    {
        innerWriter.WriteStartObject();
        // ... (snip) ...
        innerWriter.WriteEndObject();
    }
    writer.WriteRawValue(sb.ToString());
}

另一种方法(取决于您的预期结果)根本不要在转换器中使用单独的编写器,而是在序列化的最开始时创建 NullJsonWriter 实例并将其传递给外部序列化程序(就像我在本文前面的概念验证中所做的那样)。 当 Json.Net 调用您的转换器时,传递给WriteJson的编写器将是您的自定义NullJsonWriter,因此此时您可以自然地写入它,而无需跳过箍。 但是请记住,使用这种方法,整个 JSON 会将空值替换为空字符串,而不仅仅是转换器处理的IEnumerable<IGame>。 如果您试图将特殊行为限制为仅转换器,那么此方法将不适合您。 这实际上取决于您要完成的任务 - 您希望在任何地方替换空值还是仅在 JSON 的一部分中替换空值? 我从你的问题中没有很好地理解这一点。

无论如何,希望这有帮助。

 JsonSerializer _jsonWriter = new JsonSerializer
            {
                NullValueHandling = NullValueHandling.Include
            };

使用此选项可以使序列化程序不忽略 null 值,然后您应该能够使用已有的代码重写序列化:)