JSON Convert Empty String Instead of Null

Json Convert empty string instead of null

This should work:

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);
}
}

Json.net deserilize all empty strings as null

I finally found a simple, but hacky solution. I used a regular JsonConverter and backtracked using code like this:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String && (reader.Value as string == ""))
return null;

skip = true;
return serializer.Deserialize(reader, objectType);
}

private bool skip = false;
public override bool CanConvert(Type objectType) // If this is ever cached, this hack won't work.
{
if (skip)
{
skip = false;
return false;
}

return true;
}

Convert empty strings to null with Json.Net

After a lot of source digging, I solved my problem.
Turns out all the solutions proposed in the comments only work if I am deserializing a complex object which contains a property that is a string.
In this case, yes, simply modifying the contract resolver works [1].

However, what I needed was a way to convert any string to null upon deserialization, and modifying the contract this way will fail for the case where my object is just a string, i.e.,

public void MyMethod(string jsonSomeInfo)
{
// At this point, jsonSomeInfo is "\"\"",
// an emmpty string.

var deserialized = new JsonSerializer().Deserialize(new StringReader(jsonSomeInfo), typeof(string));

// deserialized = "", event if I used the modified contract resolver [1].
}

What happens is that when we work with a complex object, internally JSON.NET assigns a TokenType of JsonToken.StartObject to the reader, which will cause the deserialization to follow a certain path where property.ValueProvider.SetValue(target, value); is called.

However, if the object is just a string, the TokenType will be JsonToken.String, and the path will be different, and the value provider will never be invoked.

In any event, my solution was to build a custom converter to convert JsonReaders that have TokenType == JsonToken.String (code below).

Solution

public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;

string text = reader.Value.ToString();

if (string.IsNullOrWhiteSpace(text))
{
return null;
}

return text;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not needed because this converter cannot write json");
}

public override bool CanWrite
{
get { return false; }
}
}

[1] Credits to @Raphaël Althaus.

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

public class EmptyToNullStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;

public EmptyToNullStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}

public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);

if (_MemberInfo.PropertyType == typeof(string) && result != null && string.IsNullOrWhiteSpace(result.ToString()))
{
result = null;
}

return result;
}

public void SetValue(object target, object value)
{
if (_MemberInfo.PropertyType == typeof(string) && value != null && string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}

_MemberInfo.SetValue(target, value);
}
}

Json.Net How to deserialize null as empty string?

You could use the DefaultValue attribute.

Decorate it as

[DataMember]
[JsonProperty(PropertyName = "email", DefaultValueHandling = DefaultValueHandling.Populate)]
[StringLength(40, ErrorMessage = "The Mobile value cannot exceed 40 characters. ")]
[DefaultValue("")]
public string Email { get; set; }

Class member is json-serialized as empty string instead of null

Mathias R. Jessen's helpful answer explains the problem well: if you assign $null to a [string] typed property or variable, you'll end up with '' (the empty string).

There is an - obscure - workaround, which assumes that you have control over the values you assign:

You can assign [NullString]::Value to a [string]-type-constrained variable or property to make it store a $null value:

Class foo {
# Default to $null
[string] $X = [NullString]::Value
}

[foo]::new() | ConvertTo-Json

[foo] @{ X = [NullString]::Value } | ConvertTo-Json

($o = New-Object foo).X = [NullString]::Value
$o | ConvertTo-Json

Note:

  • The primary purpose of [NullString]::Value is to allow passing true null ($null) values to string-typed parameters of .NET Methods, which wouldn't work when passing [string]-typed PowerShell variables or properties, which on assignment invariably convert $null to '' (the empty string), as explained in Mathias' answer.

  • While you can use it in pure PowerShell code as well, as shown above, be mindful that other code that accepts [string]-typed values may not expect them to ever be $null, and that passing a value to another [string]-constrained (parameter) variable again converts to ''.

  • See this answer for more information.

The above yields:

{
"X": null
}
{
"X": null
}
{
"X": null
}

System.Text.Json Serialize null strings into empty strings globally

In .NET 5.0 this can be done by overriding JsonConverter<T>.HandleNull and returning true:

public class EmptyStringConverter : JsonConverter<string>
{
public override bool HandleNull => true;

public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> reader.TokenType == JsonTokenType.Null ? "" : reader.GetString();

public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
writer.WriteStringValue(value ?? "");
}

For more, see Handle null values.

Demo fiddle here.

In .NET Core 3.x this is not implemented. From Handle null values in .NET Core 3.x:

Handle null values

By default, the serializer handles null values as follows:

  • For reference types and Nullable<T> types:

    • It does not pass null to custom converters on serialization.
    • It does not pass JsonTokenType.Null to custom converters on deserialization.
    • It returns a null instance on deserialization.
    • It writes null directly with the writer on serialization.
  • For non-nullable value types:

    • It passes JsonTokenType.Null to custom converters on deserialization. (If no custom converter is available, a JsonException exception is thrown by the internal converter for the type.)

This null-handling behavior is primarily to optimize performance by skipping an extra call to the converter. In addition, it avoids forcing converters for nullable types to check for null at the start of every Read and Write method override.

How to deserialize an empty string to a null value for all `Nullable T ` value types using System.Text.Json?

You can use the factory converter pattern to create a JsonConverterFactory that causes an empty string to be interpreted as null for all Nullable<T> type values.

The following factory does the job:

public class NullableConverterFactory : JsonConverterFactory
{
static readonly byte [] Empty = Array.Empty<byte>();

public override bool CanConvert(Type typeToConvert) => Nullable.GetUnderlyingType(typeToConvert) != null;

public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) =>
(JsonConverter)Activator.CreateInstance(
typeof(NullableConverter<>).MakeGenericType(
new Type[] { Nullable.GetUnderlyingType(type) }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null);

class NullableConverter<T> : JsonConverter<T?> where T : struct
{
// DO NOT CACHE the return of (JsonConverter<T>)options.GetConverter(typeof(T)) as DoubleConverter.Read() and DoubleConverter.Write()
// DO NOT WORK for nondefault values of JsonSerializerOptions.NumberHandling which was introduced in .NET 5
public NullableConverter(JsonSerializerOptions options) {}

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
if (reader.ValueTextEquals(Empty))
return null;
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}

public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.Value, options);
}
}

The factory should be added to the JsonSerializerOptions.Converters collection of your framework.

Notes:

  • In my original version of this answer I cached the return of (JsonConverter<T>)options.GetConverter(typeof(T)) for performance as recommended by Microsoft. Unfortunately as noted in comments by zigzag Microsoft's own DoubleConverter.Read() and DoubleConverter.Write() methods do not account for non-default values of JsonSerializerOptions.NumberHandling, so I have removed this logic as of .NET 5.

Demo fiddle here.



Related Topics



Leave a reply



Submit