Home > Net >  System.Text.Json custom null values
System.Text.Json custom null values

Time:09-07

I'm using an api endpoint and they are returning invalid responses in some of the fields instead of null values for decimals. here is an example

{
  "name": "MyName",
  "decimalOne": "0.01",
  "decimalTwo": "None",
  "decimalThree": "-"
}

I would like my code to handle these custom "null" things they are sending me as just regular nulls. Is there a way for me to do this?

CodePudding user response:

you can try something like this, ( I don't know how many different decimal fields you have)

Name name=System.Text.Json.JsonSerializer.Deserialize<Name>(json);

public class Name
{
    public string name {get; set;}
    
    [JsonPropertyName("decimalOne")]
    public string decimalOneString  { get; set;}
    
    [System.Text.Json.Serialization.JsonIgnore]
    public decimal? decimalOne
    {
        get { if (decimal.TryParse(decimalOneString, out var result)) return result;
        return null;
        }
        set { decimalOneString = value.ToString();}
    }
}

CodePudding user response:

I've decided to do a NullableConverterFactory with additional "null" parameters we can pass in.

looks like this.

/// <summary>
/// Converter that can parse nullable types. int? decimal? etc
/// </summary>
public class NullableConverterFactory : JsonConverterFactory
{
    private static readonly byte [] EMPTY = Array.Empty<byte>();

    private static readonly HashSet<byte[]> NULL_VALUES = new()
                                                          {
                                                              EMPTY
                                                          };

    /// <summary>
    /// empty constructor.
    /// </summary>
    public NullableConverterFactory()
    {
    }

    /// <summary>
    /// Supply additional accepted null values as byte[] here.
    /// </summary>
    /// <param name="additionalNullValues"></param>
    public NullableConverterFactory(HashSet<byte[]> additionalNullValues)
    {
        foreach (byte[] nullValue in additionalNullValues)
        {
            NULL_VALUES.Add(nullValue);
        }
    }

    /// <summary>
    /// Returns true if this converter can convert the value.
    /// </summary>
    /// <param name="typeToConvert"></param>
    /// <returns></returns>
    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, NULL_VALUES },
                                                culture: null);

    class NullableConverter<T> : JsonConverter<T?> where T : struct
    {
        private readonly HashSet<byte[]> _nullValues;

        // 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, HashSet<byte[]> nullValues)
        {
            _nullValues = nullValues;
        }

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

            foreach (byte[] nullValue in _nullValues)
            {
                if (reader.ValueTextEquals(nullValue))
                {
                    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);
    }
}

usage:

new JsonSerializerOptions()
          {
              PropertyNameCaseInsensitive = true,
              Converters =
              {
                  new NullableConverterFactory(new HashSet<byte[]>
                                               {
                                                   Encoding.UTF8.GetBytes("-"),
                                                   Encoding.UTF8.GetBytes("None")
                                               })
              },
              NumberHandling      = JsonNumberHandling.AllowReadingFromString,
              AllowTrailingCommas = true
          };
  • Related