Deserialize JSON in a "Tryparse" Way

Deserialize json in a TryParse way

With Json.NET you can validate your json against a schema:

 string schemaJson = @"{
'status': {'type': 'string'},
'error': {'type': 'string'},
'code': {'type': 'string'}
}";

JsonSchema schema = JsonSchema.Parse(schemaJson);

JObject jobj = JObject.Parse(yourJsonHere);
if (jobj.IsValid(schema))
{
// Do stuff
}

And then use that inside a TryParse method.

public static T TryParseJson<T>(this string json, string schema) where T : new()
{
JsonSchema parsedSchema = JsonSchema.Parse(schema);
JObject jObject = JObject.Parse(json);

return jObject.IsValid(parsedSchema) ?
JsonConvert.DeserializeObject<T>(json) : default(T);
}

Then do:

var myType = myJsonString.TryParseJson<AwsomeType>(schema);

Update:

Please note that schema validation is no longer part of the main Newtonsoft.Json package, you'll need to add the Newtonsoft.Json.Schema package.

Update 2:

As noted in the comments, "JSONSchema" have a pricing model, meaning it isn't free. You can find all the information here

c# : Deserialize json Object

You can first check for empty response, and in try-catch try to parse array. If it's not array, it would throw exception that you can catch, and parse the Json object:

string resp; //this is where you will store your response from server
JArray array;
JObject json;
if(resp == "<Empty JSON content>")
{
Console.WriteLine("Response is empty json");
}
else
{
try
{
array = JArray.Parse(resp);
Console.WriteLine("Array parsed");
}
catch (Newtonsoft.Json.JsonException ex)
{
try
{
json = JObject.Parse(resp);
Console.WriteLine("error parsed");
}
catch(Newtonsoft.Json.JsonException ex2)
{
Console.WriteLine("Response was not json object");
}
}
}

Can I determine whether the string can deserialize by newtonsoft?

There is no TryParse in Json.Net as of the current release. If you don't have a known schema to validate against, and you don't want to use try...catch then your only other option that I can see is to attach an error handler to the serializer and use that as a means of detecting and/or handling errors. See "Error Handling" in the documentation.

How to deserialize json where the data can be an object or an empty array and as an object is in a Dictionary format?

I believe code from answers you're referring to, is working. I tested your case like this:

public class CountryResponseData
{
public CountryData Data { get; set; }
}

public class CountryData
{
[JsonConverter(typeof(EmptyArrayOrDictionaryConverter))]
public Dictionary<string, Country> Countries { get; set; }
}

public class Country
{
public string Code { get; set; }
}

// this a modified version of this SO-answer: https://stackoverflow.com/a/45505097/14072498
public class EmptyArrayOrDictionaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType.IsAssignableFrom(typeof(Dictionary<string, object>));

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return token.Type switch
{
JTokenType.Object => token.ToObject(objectType, serializer), JTokenType.Array when !token.HasValues => Activator.CreateInstance(objectType),
_ => throw new JsonSerializationException("Object or empty array expected")
};
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => serializer.Serialize(writer, value);
}

public class MyJsonTestClass
{
private const string JsonWithCountries = @"{
""data"": {
""Countries"": {
""818"": {
""id"": ""818"",
""code"": ""EG"",
""name"": ""Egypt""
},
""414"": {
""id"": ""414"",
""code"": ""KW"",
""name"": ""Kuwait""
},
""682"": {
""id"": ""682"",
""code"": ""SA"",
""name"": ""Saudi Arabia""
},
""784"": {
""id"": ""784"",
""code"": ""AE"",
""name"": ""United Arab Emirates""
}
},
""Regions"": [],
""Cities"": [],
""Exclude"": []
}
}";

private const string JsonWithoutCountries = @"{
""data"": {
""Countries"": [],
""Regions"": [],
""Cities"": [],
""Exclude"": []
}
}";

[Test]
public void MyJsonTest()
{
// START tests using NewtonSoft

var result = JsonConvert.DeserializeObject<CountryResponseData>(JsonWithCountries);
Assert.NotNull(result?.Data);

var result2 = JsonConvert.DeserializeObject<CountryResponseData>(JsonWithoutCountries);
Assert.NotNull(result2?.Data);
}
}

System.Text.Json deserialize uint

If you're on .NET 5, or if - as @Jimi pointed out - install <PackageReference Include="System.Text.Json" Version="5.0.2" />, you can then use IncludeFields and AllowReadingFromString options:

var serializeOptions = new JsonSerializerOptions
{
IncludeFields = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};

var p = JsonSerializer.Deserialize<Payload>(json, serializeOptions);

Before .NET5 you'll need to change ModeId to be a property.


BTW. You could deal with string/int with no converter:

public string ModeId {get; set}
public uint ModeIdParsed => uint.Parse(ModeId);

If you need to keep ModeId name then you could

public class Payload
{
[JsonPropertyName("ModeId")]
public string modeId {get;set;}

[JsonIgnore]
public uint ModeId => uint.Parse(modeId);
}

Unfortunately in 3.1 you'll need to keep the string variant public (deserializing privates is available from v5).

System.Text.Json: Deserialize JSON with automatic casting

Edit: You can use JsonNumberHandlingAttribute and it handles everything correctly in 1 line, no need to write any code:

[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public class HomeController : Controller
....

Original answer:

  1. The new System.Text.Json api exposes a JsonConverter api which allows us to convert the type as we like.

    For example, we can create a generic number to string converter:

    public class AutoNumberToStringConverter : JsonConverter<object>
    {
    public override bool CanConvert(Type typeToConvert)
    {
    return typeof(string) == typeToConvert;
    }
    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
    if(reader.TokenType == JsonTokenType.Number) {
    return reader.TryGetInt64(out long l) ?
    l.ToString():
    reader.GetDouble().ToString();
    }
    if(reader.TokenType == JsonTokenType.String) {
    return reader.GetString();
    }
    using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
    return document.RootElement.Clone().ToString();
    }
    }

    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
    {
    writer.WriteStringValue( value.ToString());
    }
    }
  2. When working with MVC/Razor Page, we can register this converter in startup:

    services.AddControllersWithViews().AddJsonOptions(opts => {
    opts.JsonSerializerOptions.PropertyNameCaseInsensitive= true;
    opts.JsonSerializerOptions.Converters.Insert(0, new AutoNumberToStringConverter());
    });

    and then the MVC/Razor will handle the type conversion automatically.

  3. Or if you like to control the serialization/deserialization manually:

    var opts = new JsonSerializerOptions {
    PropertyNameCaseInsensitive = true,
    };
    opts.Converters.Add(new AutoNumberToStringConverter());
    var o = JsonSerializer.Deserialize<Product>(json,opts) ;
  4. In a similar way you can enable string to number type conversion as below :

    public class AutoStringToNumberConverter : JsonConverter<object>
    {
    public override bool CanConvert(Type typeToConvert)
    {
    // see https://stackoverflow.com/questions/1749966/c-sharp-how-to-determine-whether-a-type-is-a-number
    switch (Type.GetTypeCode(typeToConvert))
    {
    case TypeCode.Byte:
    case TypeCode.SByte:
    case TypeCode.UInt16:
    case TypeCode.UInt32:
    case TypeCode.UInt64:
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
    case TypeCode.Decimal:
    case TypeCode.Double:
    case TypeCode.Single:
    return true;
    default:
    return false;
    }
    }
    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
    if(reader.TokenType == JsonTokenType.String) {
    var s = reader.GetString() ;
    return int.TryParse(s,out var i) ?
    i :
    (double.TryParse(s, out var d) ?
    d :
    throw new Exception($"unable to parse {s} to number")
    );
    }
    if(reader.TokenType == JsonTokenType.Number) {
    return reader.TryGetInt64(out long l) ?
    l:
    reader.GetDouble();
    }
    using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
    throw new Exception($"unable to parse {document.RootElement.ToString()} to number");
    }
    }

    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
    {
    var str = value.ToString(); // I don't want to write int/decimal/double/... for each case, so I just convert it to string . You might want to replace it with strong type version.
    if(int.TryParse(str, out var i)){
    writer.WriteNumberValue(i);
    }
    else if(double.TryParse(str, out var d)){
    writer.WriteNumberValue(d);
    }
    else{
    throw new Exception($"unable to parse {str} to number");
    }
    }
    }


Related Topics



Leave a reply



Submit