JsonConvert.DeserializeObject with different property types needs to continue even after failing but collect the list of errors

74 views Asked by At

I need to deserialize a class with different types (int, float, string, guid) of properties. I am hoping to find a way to do this so that:

  • if a value does not match it's expected type an error would be added to an error list
  • and continue with the deserialization to have the full list of errors rather than just the first one

Example

public record Row()    

[JsonProperty("columnName")]
public string? ColumnName { get; set; }

[JsonProperty("entityCode")]
public Guid? EntityCode { get; set; }

[JsonProperty("numberValue")]
public float? NumberValue { get; set; }

[JsonProperty("integerValue")]
public int? IntegerValue { get; set; }

[JsonProperty("booleanValue")]
public bool? BooleanValue { get; set; }

Object example

"Row": [
{
    "columnName": "category",
    "entityCode": "not a guid"
},
{
    "columnName": "numberValue",
    "numberValue": "not a number"
},
{
    "columnName": "value",
    "stringValue": 104.28398
},
{
    "columnName": "value",
    "booleanValue": "not a bool"
}

],

And then I need to defserialize the object above JsonConvert.DeserializeObject<IList<Row>>

But instead of instantly jumping to an exception with one error I need to know all the properties that have wrong types.

Can this be done?

1

There are 1 answers

4
Power Mouse On BEST ANSWER

did not provided correct class structure. you can do with custom serializing settings

void Main()
{
    string json = "{\"Row\":[{\"columnName\":\"category\",\"entityCode\":\"not a guid\"},{\"columnName\":\"numberValue\",\"numberValue\":\"not a number\"},{\"columnName\":\"value\",\"stringValue\":104.28398},{\"columnName\":\"value\",\"booleanValue\":\"not a bool\"}]}";
    
    var errors = new List<string>();
    var settings = new JsonSerializerSettings()
    {
        Error = (s, e) =>
        {
            var _Obj = e.CurrentObject as Row;
            if (_Obj != null)
            {
                errors.Add($"Error on {_Obj.ColumnName}: {e.ErrorContext.Error.Message}");
            }
            else
            {
                errors.Add(e.ErrorContext.Error.Message);
            }
            e.ErrorContext.Handled = true;
        }
    };
    JsonConvert.DeserializeObject<Root>(json, settings).Dump();
    if(errors.Count>0)
        errors.Dump();
}

public class Root
{
    public List<Row> Row { get; set; }
}
public record Row()
{

    [JsonProperty("columnName")]
    public string ColumnName { get; set; }

    [JsonProperty("entityCode")]
    public Guid? EntityCode { get; set; }

    [JsonProperty("numberValue")]
    public float? NumberValue { get; set; }

    [JsonProperty("integerValue")]
    public int? IntegerValue { get; set; }

    [JsonProperty("booleanValue")]
    public bool? BooleanValue { get; set; }
}

results: enter image description here

UPDATE: this is definetly XY problem

void Main()
{
    string json = "{\"Row\":[{\"columnName\":\"category\",\"entityCode\":\"not a guid\"},{\"columnName\":\"numberValue\",\"numberValue\":\"not a number\"},{\"columnName\":123,\"stringValue\":104.28398},{\"columnName\":\"value\",\"booleanValue\":\"not a bool\"}]}";

    var errors = new List<string>();
    var settings = new JsonSerializerSettings()
    {
        Error = (s, e) =>
        {
            var _Obj = e.CurrentObject as Row;
            if (_Obj != null)
            {
                errors.Add($"Error on {_Obj.ColumnName}: {e.ErrorContext.Error.Message}");
            }
            else
            {
                errors.Add(e.ErrorContext.Error.Message);
            }
            e.ErrorContext.Handled = true;
        }
    };
    JsonConvert.DeserializeObject<Root>(json, settings).Dump();
    if (errors.Count > 0)
        errors.Dump();
}

public class Root
{
    public List<Row> Row { get; set; }
}
public record Row()
{
    [JsonConverter(typeof(StrictStringConverter))]
    [JsonProperty("columnName")]
    public string ColumnName { get; set; }

    [JsonProperty("entityCode")]
    public Guid? EntityCode { get; set; }

    [JsonProperty("numberValue")]
    public float? NumberValue { get; set; }

    [JsonProperty("integerValue")]
    public int? IntegerValue { get; set; }

    [JsonProperty("booleanValue")]
    public bool? BooleanValue { get; set; }
}

public class StrictStringConverter : JsonConverter
{
    readonly JsonSerializer defaultSerializer = new JsonSerializer();

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsIntegerType();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.String:
                    return defaultSerializer.Deserialize(reader, objectType);
            case JsonToken.Integer:
            case JsonToken.Float: // Accepts numbers like 4.00
                throw new JsonSerializationException($"Value \"{reader.Value}\" of type {reader.TokenType} was not a string");
            case JsonToken.Null:
                return defaultSerializer.Deserialize(reader, objectType);
            default:
                return defaultSerializer.Deserialize(reader, objectType);
                
        }
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class JsonExtensions
{
    public static bool IsIntegerType(this Type type)
    {
        type = Nullable.GetUnderlyingType(type) ?? type;
        if (type == typeof(long)
            || type == typeof(ulong)
            || type == typeof(int)
            || type == typeof(uint)
            || type == typeof(short)
            || type == typeof(ushort)
            || type == typeof(byte)
            || type == typeof(sbyte)
            || type == typeof(System.Numerics.BigInteger))
            return true;
        return false;
    }
}

enter image description here