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:
The new
System.Text.Json
api exposes aJsonConverter
api which allows us to convert the type as we like.For example, we can create a generic
number
tostring
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());
}
}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.
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) ;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
Entity Framework - Retrieve Id Before 'Savechanges' Inside a Transaction
How to Tell the Data Annotations Validator to Also Validate Complex Child Properties
What's a Good Threadsafe Singleton Generic Template Pattern in C#
String.Replace() VS. Stringbuilder.Replace()
Read Large Txt File Multithreaded
Line Intersection with Aabb Rectangle
How to Download a File to Browser from Azure Blob Storage
How to Execute an X86 Assembly Sequence from Within C#
Send Http Post Message in ASP.NET Core Using Httpclient Postasjsonasync
How to Get the Network Interface and Its Right Ipv4 Address
Write Values in App.Config File
ASP.NET MVC Dropdown List from Selectlist
Performance of Find() VS. Firstordefault()
Declaring a Variable Inside or Outside an Foreach Loop: Which Is Faster/Better
What Is Passing Parameters to SQL and Why Do I Need It
How to Get All Subsets of an Array
How to Use Default Serialization in a Custom System.Text.JSON JSONconverter