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

时间:2016-01-23 00:57:02

标签: c# json serialization json.net

我想将字符串和可空类型的所有空值序列化为空字符串。我查看了使用自定义JsonConverter但是带有空值的属性不会传递到转换器中。我研究了使用合约解析器和值提供程序,但它不允许我将int?的值设置为空字符串。 最有希望的是使用覆盖JsonWriter方法的自定义WriteNull,但是当我在转换器的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文本,但它实际上并没有写到我的网页上。

2 个答案:

答案 0 :(得分:3)

我不确定我是否理解为什么要写出一个空字符串来代替所有空值,甚至那些可能代表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,它将被传递到writer方法的WriteJson参数。您没有写入该实例,而是使用新的NullJsonWriter实例覆盖此变量。但请记住,替换引用变量的内容不会替换变量引用的原始实例。您已成功写入转换器中的新编写器实例,但您正在编写的所有输出都将进入StringBuilder方法开头实例化的WriteJson。由于您从未从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>。如果您试图将特殊行为限制在转换器中,那么这种方法对您不起作用。这实际上取决于你想要实现的目标 - 你想要将null替换到任何地方或只是部分JSON吗?我从你的问题中没有很好地理解这一点。

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

答案 1 :(得分:0)

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

使用此命令使序列化程序不会忽略空值,那么您应该能够使用已有的代码覆盖序列化:)

相关问题