5

I want to omit empty string properties from my Json.

Here's what I did.

Create a Custom Converter that converts empty strings to null:

public class EmptyStringToNullConverter : JsonConverter<string>
{
    public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetString();
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        if (value == string.Empty)
            writer.WriteNullValue();
        else
            writer.WriteStringValue(value);
    }
}
    

I have a JsonSerializerClass, which basically is a wrapper around System.Text.Json.JsonSerializer and this class is using the Converter:

    public class JsonSerializerClass
{
    private JsonSerializerOptions serializeOptions;
    private JsonSerializerOptions deserializeOptions;

    public JsonSerializerClass()
    {
        serializeOptions = new JsonSerializerOptions();
        deserializeOptions = new JsonSerializerOptions();
        InitSerializer();
        InitDeserializer();
    }

    public JsonSerializerOptions SerializeOptions => serializeOptions;

    public string Serialize(object value)
    {
        return Serialize(value, serializeOptions);
    }

    public T Deserialize<T>(string json)
    {
        var o = System.Text.Json.JsonSerializer.Deserialize<T>(json, deserializeOptions);

        if (o == null)
            throw new ArgumentException($"Cannot deserialize JSON to type {typeof(T)}: {json}");

        return o;
    }

    public static string Serialize(object value, JsonSerializerOptions options)
    {
        return System.Text.Json.JsonSerializer.Serialize(value, options);
    }

    private void InitSerializer()
    {
        serializeOptions.Converters.Add(new EmptyStringToNullConverter());

        serializeOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    }

    private void InitDeserializer()
    {
        deserializeOptions.PropertyNameCaseInsensitive = true;

        deserializeOptions.Converters.Add(new JsonStringEnumConverter());
        deserializeOptions.Converters.Add(new DateOnlyConverter());
        deserializeOptions.Converters.Add(new DateOnlyNullableConverter());
        deserializeOptions.Converters.Add(new IntConverter());
        deserializeOptions.Converters.Add(new DecimalConverter());

        deserializeOptions.IncludeFields = true;
        deserializeOptions.AllowTrailingCommas = true;
        deserializeOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
        deserializeOptions.ReadCommentHandling = JsonCommentHandling.Skip;
    }
}    

And here's my test case:

var person = new Person
{
    Name = "John",
    LastName = "Doe",
    MiddleName = ""
};
var serializer = new JsonSerializerClass();

var json = serializer.Serialize(person);

json.ToLower().Should().NotContain(nameof(person.MiddleName).ToLower());

I would expect that MiddleName is not in the json because empty string becomes null and null values should be removed from the json. But instead MiddleName is in the json, with value null!

3
  • I think you missed setting IgnoreNullValues to true on your deserializeOptions instance. Besides that can't say i like handling empty strings like null but that's up to you. Oh my bad that one seems to be obsolete now. Commented Jan 13, 2023 at 15:47
  • I guess, a converter is too late. You probably need this: learn.microsoft.com/en-us/dotnet/standard/serialization/… Because just writing nothing at all will disrupt the json: dotnetfiddle.net/layxIO Commented Jan 13, 2023 at 15:53
  • Different guess i would assume the Option prevents that a JsonConverter writes anything but your custom JsonConverter takes the option but then ignores it and always calls writer.WriteNullValue(); Commented Jan 13, 2023 at 15:53

2 Answers 2

6

In .NET 7, you can use a Modifier to influence the serialization process (more information in the announcement blog):

static void ExcludeEmptyStrings(JsonTypeInfo jsonTypeInfo)
{
    if (jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
        return;

    foreach (JsonPropertyInfo jsonPropertyInfo in jsonTypeInfo.Properties)
    {
        if (jsonPropertyInfo.PropertyType == typeof(string))
        {
            jsonPropertyInfo.ShouldSerialize = static (obj, value) => 
                !string.IsNullOrEmpty((string)value);
        }
    }
}

In this example, it's all about the ShouldSerialize property, which takes in the value and allows you to decide whether or not it should be serialized.

Add the modifier using the property I linked above:

var jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = { ExcludeEmptyStrings }
    }
};

.NET Fiddle

Sign up to request clarification or add additional context in comments.

2 Comments

Yes, I came across this too, but I'm using ,net core 6...
Fair enough. I thought that could be the case, but I didn't see any mention of .NET 6 in your question.
-1

you can try this custom converter

    var options = new System.Text.Json.JsonSerializerOptions { WriteIndented = true };
    options.Converters.Add(new IgnoreNullOrEmptyStringJsonConverter<Person>());
    var json = System.Text.Json.JsonSerializer.Serialize(person, options);

public class IgnoreNullOrEmptyStringJsonConverter<T> : System.Text.Json.Serialization.JsonConverter<T> where T : class
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Use default implementation when deserializing (reading)
        return System.Text.Json.JsonSerializer.Deserialize<T>(ref reader, options);
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        using (JsonDocument document = System.Text.Json.JsonSerializer.SerializeToDocument(value))
        {
            foreach (var property in document.RootElement.EnumerateObject())
            {
                if (property.Value.ValueKind.ToString() != "Null" 
                   && !(property.Value.ValueKind.ToString() == "String" 
                   && string.IsNullOrEmpty(property.Value.ToString())))
                    property.WriteTo(writer);
            }
        }
        writer.WriteEndObject();
    }
}

output

{
  "Name": "John",
  "LastName": "Doe"
}

or you can use this function

    var json = GetIgnoreNullOrEmptyStringJson(person);


public string GetIgnoreNullOrEmptyStringJson<T>(T value) where T : class
{
    var jNode = System.Text.Json.JsonSerializer.SerializeToNode(value);

    var props = jNode.AsObject()
        .Where(prop => (prop.Value == null
               || (prop.Value.GetValue<JsonElement>().ValueKind.ToString() == "String"
               && string.IsNullOrEmpty(prop.Value.ToString()))))
        .Select(e => e)
        .ToArray();

    for (var i = 0; i < props.Length; i++)
        jNode.AsObject().Remove(props[i].Key);

    return System.Text.Json.JsonSerializer.Serialize(jNode, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
}

2 Comments

Thanks for the suggestion, but now I get "MiddleName": {}, I think it isn't possible with Conververts.. It seems like Converters are applied very late in the serialization process.
@Martijn As you understand this code was tested and working properly. See my output

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.