0

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?

kewur
  • 421
  • 5
  • 15
  • Perhaps implementing a custom JsonConverter (https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to). In the converter, you can then handle and convert the json strings however you need. –  Sep 05 '22 at 19:48
  • 1
    How is `decimalOne` defined in your model? Is it `public decimal decimalOne { get; set; }`? Or is it nullable, i.e. `public decimal? decimalOne { get; set; }`? Or something else? Regarding converters as suggested by @MySkullCaveIsADarkPlace, see [System.Text.Json: Convert JSON with a decimal value "NA"](https://stackoverflow.com/q/71795482) which looks to be close to what you require. And actually, does that question answer yours sufficiently, or do you need more help? – dbc Sep 05 '22 at 20:07
  • I think there's merit in the idea behind @Serge's answer. You may want to keep the raw deserialized JSON properties as strings and handle translation to your desired model separately rather than trying to do it transparently. It'll help the maintainability of your code going forward. – HasaniH Sep 05 '22 at 23:07
  • @dbc the fields are nullable. thanks, I was able to come up with a solution that works for me. there was another post about nullable deserializations (I cant find the post now), I've modified that to have other null values in there. – kewur Sep 07 '22 at 03:37

2 Answers2

1

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();}
    }
}
Serge
  • 40,935
  • 4
  • 18
  • 45
0

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
          };
kewur
  • 421
  • 5
  • 15