将Json转换为空字符串而不是null

49

我正在尝试对我的结构体进行序列化,以便未设置值的字符串会得到默认值""而不是null。

[JsonProperty(PropertyName = "myProperty", DefaultValueHandling = DefaultValueHandling.Populate)]
[DefaultValue("")]
public string MyProperty{ get; set; }

我的JSON字符串中的结果:

"myProperty": null,

我想要什么

"myProperty": "",

我还尝试创建一个转换器,但没有效果,因为某些原因Can Convert和WriteJson函数甚至都没有触发:

[JsonProperty(PropertyName = "myProperty")]
[JsonConverter(typeof(NullToEmptyStringConverter))]
public string MyProperty{ get; set; }

class NullToEmptyStringConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(object[]);
    }

    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)
    {
        if (value == null)
            writer.WriteValue("");
    }
}

这也没有帮助 Json.Net如何将null反序列化为空字符串?


可能是 Serializing null in JSON.NET 的重复内容。 - Christian Phillips
已经尝试过了,但仍然无效。 - Igor Meszaros
1
DefaultValue 属性的作用并不是为属性赋予默认值。它只是作为序列化的信号,如果在序列化时该属性具有该值,则无需序列化,因为它将通过默认方式获得该值。如果您实际上没有为该属性提供默认值,那么您实际上正在滥用该属性。 - Lasse V. Karlsen
7个回答

44

这应该可以正常工作:

var settings = new JsonSerializerSettings() { ContractResolver= new NullToEmptyStringResolver() };
var str = JsonConvert.SerializeObject(yourObj, settings);

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;

public class NullToEmptyStringResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        return type.GetProperties()
                .Select(p=>{
                    var jp = base.CreateProperty(p, memberSerialization);
                    jp.ValueProvider = new NullToEmptyStringValueProvider(p);
                    return jp;
                }).ToList();
    }
}

public class NullToEmptyStringValueProvider : IValueProvider
{
    PropertyInfo _MemberInfo;
    public NullToEmptyStringValueProvider(PropertyInfo memberInfo)
    {
        _MemberInfo = memberInfo;
    }

    public object GetValue(object target)
    {
        object result =  _MemberInfo.GetValue(target);
        if (_MemberInfo.PropertyType == typeof(string) && result == null) result = "";
        return result;
            
    }

    public void SetValue(object target, object value)
    {
        _MemberInfo.SetValue(target, value);
    }
}

我已经设置了另一个合同解析器。似乎没有办法拥有多个。 - Joel Dean
3
如果您想进行如此多的更改,请提供另一个答案,不要编辑此答案。我已经将其回滚了。 - L.B
1
我曾经遇到需要多个ContractResolvers的情况。没有选项可以有多个解析器,但您可以从其他解析器继承。这是一个很好的例子https://dev59.com/pVkS5IYBdhLWcg3wn4C- 例如,在应用了NullToEmptyStringResolver之后,所有JSON响应都具有小写字段名称。从CamelCasePropertyNamesContractResolver继承它对我很有效。 - T.Nylund
1
这个解决方案破坏了我正在序列化的对象中的其他几个项目。请考虑使用Kirill Shlenski下面的解决方案。 - Tyler Findlay
@L.B 对于可空整数,它不起作用。 - Vineet Agarwal

31
虽然采纳的答案指引了我正确的方向,但它看起来非常脆弱。当Json.NET有完美的功能工具可用于解决JsonProperty对象列表并实现IValueResolver时(这些工具可能具有各种优化和角落情况处理内置,而基于反射重新实现则没有这些内置) ,我不想为此担心。
我的解决方案执行最小的覆盖和解析器替代,以确保只有必须更改的部分才被实际更改:
public sealed class SubstituteNullWithEmptyStringContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.PropertyType == typeof(string))
        {
            // Wrap value provider supplied by Json.NET.
            property.ValueProvider = new NullToEmptyStringValueProvider(property.ValueProvider);
        }

        return property;
    }

    sealed class NullToEmptyStringValueProvider : IValueProvider
    {
        private readonly IValueProvider Provider;

        public NullToEmptyStringValueProvider(IValueProvider provider)
        {
            if (provider == null) throw new ArgumentNullException("provider");

            Provider = provider;
        }

        public object GetValue(object target)
        {
            return Provider.GetValue(target) ?? "";
        }

        public void SetValue(object target, object value)
        {
            Provider.SetValue(target, value);
        }
    }
}

24

嗯,我的解决方案非常简单,但不使用JSON.NET功能,只需将“backend”字段添加到您的属性中:

public class Test
{
    private string _myProperty = string.Empty;

    [JsonProperty(PropertyName = "myProperty")]
    public string MyProperty
    {
        get { return _myProperty; }
        set { _myProperty = value; }
    }
}

编辑:

C# 6.0 将支持属性初始化:

public class Test
{
    [JsonProperty(PropertyName = "myProperty")]
    public string MyProperty { get; set;} = "";
}

1
我会避免这样做,因为我有超过20个属性。但如果其他方法都失败了,我想我将没有其他选择...谢谢你的回复! - Igor Meszaros
我刚在我正在开发的应用程序中尝试了这个,如果你使用一个映射器(在我的情况下是ValueInjecter)设置值,则无法按预期工作。 - shawty
5
没有任何阻止你调用 MyProperty = null; 从而将其返回为空字符串。 - Gilbert
@Gilbert 我没想到那个。谢谢。 - Nico

8

@Kirill Shlenskiy的解决方案很好,但它没有考虑NullValueHandling属性。

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Remark{ get; set; }

这是一个改进版,可以解决这个问题。如果设置了NullValueHandling.Ignore并且值为null,它将在JSON输出中被跳过。

public sealed class SubstituteNullWithEmptyStringContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (property.PropertyType == typeof(string))
        {
            // Wrap value provider supplied by Json.NET.
            property.ValueProvider = new NullToEmptyStringValueProvider(property.ValueProvider, property.NullValueHandling);
        }
        return property;
    }

    sealed class NullToEmptyStringValueProvider : IValueProvider
    {
        private readonly IValueProvider Provider;
        private readonly NullValueHandling? NullHandling;

        public NullToEmptyStringValueProvider(IValueProvider provider, NullValueHandling? nullValueHandling)
        {
            Provider = provider ?? throw new ArgumentNullException("provider");
            NullHandling = nullValueHandling;
        }

        public object GetValue(object target)
        {
            if (NullHandling.HasValue 
                && NullHandling.Value == NullValueHandling.Ignore
                && Provider.GetValue(target) == null )
            {
                return null;
            }
            return Provider.GetValue(target) ?? "";
        }

        public void SetValue(object target, object value)
        {
            Provider.SetValue(target, value);
        }
    }
}

1

原始类不是我的。感谢像您一样为此做出贡献的众多人!

我添加并解决了空值问题。

Public Class JsonBooleanConverter
    Inherits JsonConverter

    Public Status As String
    Public ErrorCode As String
    <JsonProperty(NullValueHandling:=NullValueHandling.Ignore)>
    Public ErrorMessage As String
    Public Overrides ReadOnly Property CanWrite As Boolean
        Get
            Return False
        End Get
    End Property

    Public Overrides Sub WriteJson(ByVal writer As JsonWriter, ByVal value As Object, ByVal serializer As JsonSerializer)
        Throw New NotImplementedException()
    End Sub

    Public Overrides Function ReadJson(ByVal reader As JsonReader, ByVal objectType As Type, ByVal existingValue As Object, ByVal serializer As JsonSerializer) As Object

        If IsNothing(reader.Value) Then
            Return If(existingValue, String.Empty)
        End If

        Dim value = reader.Value.ToString().ToLower().Trim()

        If objectType = GetType(Boolean) Then
            Select Case value
                Case "true", "yes", "y", "1"
                    Return True

                Case Else
                    Return False
            End Select

        ElseIf objectType = GetType(DateTime) Then
            Return If(existingValue, String.Empty)
        End If
        Return If(existingValue, String.Empty)
        'Return False
    End Function

    Public Overrides Function CanConvert(ByVal objectType As Type) As Boolean
        If objectType = GetType(Boolean) Then
            Return True
        ElseIf objectType = GetType(DateTime) Then
            Return True
        End If

        Return False
    End Function

End Class

使用方法:

Dim listObjs As List(Of YourClass) = JsonConvert.DeserializeObject(Of List(Of YourClass))(responseFromServer, New JsonBooleanConverter())

或者:

Dim listObjs As YourClass= JsonConvert.DeserializeObject(Of YourClass)(responseFromServer, New JsonBooleanConverter())

1

另一种解决方案(可能更加简洁)。您可以创建自己的JsonConverter类。

class JsonNullToEmptyStringConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    {
        return existingValue ?? string.Empty;
    }

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

写好后,您可以将其作为属性附加到您的属性上:

[JsonConverter(typeof(JsonNullToEmptyStringConverter))]
public string CommentType { get; set; }

-1

使用 System.Text.Json 和 .NET Core 3.0,这对我起作用:

var jsonSerializerOptions = new JsonSerializerOptions()
{
    IgnoreNullValues = true
};
var myJson = JsonSerializer.Serialize(myObject, 
jsonSerializerOptions );

使用.NET 6,这是解决方案:

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

在此处查看 https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-ignore-properties?pivots=dotnet-6-0



使用Newtonsoft: https://www.newtonsoft.com/json/help/html/NullValueHandlingIgnore.htm

, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }

Person person = new Person
{
   Name = "Nigal Newborn",
   Age = 1
};

    string jsonIncludeNullValues = JsonConvert.SerializeObject(person, Formatting.Indented);

Console.WriteLine(jsonIncludeNullValues);
// {
//   "Name": "Nigal Newborn",
//   "Age": 1,
//   "Partner": null,
//   "Salary": null
// }

string jsonIgnoreNullValues = JsonConvert.SerializeObject(person, 
Formatting.Indented,  new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore
 } );

Console.WriteLine(jsonIgnoreNullValues);
// {
//   "Name": "Nigal Newborn",
//   "Age": 1
// }

该解决方案没有满足请求,即当数值为“empty”时,“Partner”的输出应为空。 - Nico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接