How to handle null/empty values in JsonConvert.DeserializeObject - c#

I have the following code:
return (DataTable)JsonConvert.DeserializeObject(_data, (typeof(DataTable)));
Then, I tried:
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
return (DataTable)JsonConvert.DeserializeObject<DataTable>(_data, jsonSettings);
The return line is throwing the error:
{"Error converting value \"\" to type 'System.Double'."}
Lots of solutions online suggesting creating custom Class with nullable types but this won't work for me. I can't expect the json to be in a certain format. I have no control over the column count, column type, or column names.

You can supply settings to JsonConvert.DeserializeObject to tell it how to handle null values, in this case, and much more:
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
var jsonModel = JsonConvert.DeserializeObject<Customer>(jsonString, settings);

An alternative solution for Thomas Hagström, which is my prefered, is to use the property attribute on the member variables.
For example when we invoke an API, it may or may not return the error message, so we can set the NullValueHandling property for ErrorMessage:
public class Response
{
public string Status;
public string ErrorCode;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string ErrorMessage;
}
var response = JsonConvert.DeserializeObject<Response>(data);
The benefit of this is to isolate the data definition (what) and deserialization (use), the deserilazation needn’t to care about the data property, so that two persons can work together, and the deserialize statement will be clean and simple.

You can subscribe to the 'Error' event and ignore the serialization error(s) as required.
static void Main(string[] args)
{
var a = JsonConvert.DeserializeObject<DataTable>("-- JSON STRING --", new JsonSerializerSettings
{
Error = HandleDeserializationError
});
}
public static void HandleDeserializationError(object sender, ErrorEventArgs errorArgs)
{
var currentError = errorArgs.ErrorContext.Error.Message;
errorArgs.ErrorContext.Handled = true;
}

ASP.NET CORE:
The accepted answer works perfectly. But in order to make the answer apply globally, in startup.cs file inside ConfigureServices method write the following:
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
The answer has been tested in a .Net Core 3.1 project.

Related

Array of enums deserialization error, EF Core

I have a few columns that contain JSON data. These particular columns are an array of enums.
When querying the MembershipType table, the serialization and deserialization works well and as expected. However, I have a SQL view that nests an array of MembershipTypes and doing causes EF Core to throw an error similar to this:
System.Exception: "Error converting value \"[\"Certified\"]\" to type 'DataLayer.Models.Members.MembershipTypeCategory[]'. Path '[0].History.MembershipType.Categories', line 1, position 585."
You can see all the extra quotes and backslashes that are added to the value. I have tried string replacing the quotes and backslashes, which works, but this can have bad affects with other string data.
Any ideas?
Code below:
SQL VIEW
SELECT
hist.*
, (
select * from Member.MembershipTypes
where id = hist.MembershipTypeId and Deleted = 0
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) as MembershipType
...
Enum
public enum MembershipTypeCategory
{
Type1,
Type2,
Type3
}
and the class it's on:
[Table("MembershipTypes", Schema = "Member")]
public class MembershipType : EntityBase, IMembershipTypeDto
{
...
[NotMapped]
public MembershipTypeCategory[] Categories { get; set; }
...
}
In the ModelBuilder I have:
var settings = new JsonSerializerSettings
{
ContractResolver = new IgnoreVirtualResolver(),
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
Formatting = Formatting.None,
Error = HandleDeserializationError
};
modelBuilder.Entity<MembershipType>()
.Property(x => x.Categories)
.HasConversion(
v => JsonConvert.SerializeObject(v, Formatting.None, settings),
v => JsonConvert.DeserializeObject<MembershipTypeCategory[]>(v, settings)
);
return modelBuilder;
I think your converter is not ok. I generate code like your (in EF code first) and read/write data is ok, but string stored in db is:
{"$type":"XXX.Model.Tests.TestType[], XXX.Model","$values":[1,2]}
You have to think about converter again ;)
sample solution:
public static string ToJson(Enum[] values)
{
return ((values?.Length ?? 0) == 0) ? return "[]" : $"['{string.Join("','", values)}']";
}
public static Enum[] FromJson(string json)
{
return JsonConvert.DeserializeObject<Enum[]>(json);
}
Or using methods from JavaScriptSerializer - JSON serialization of enum as string

AddNewtonsoftJson is not overriding System.Text.Json

I have upgraded my version of .Net Core from preview 2 to preview 6 which has broken a couple of things. Most significant is that I cannot use newtonsoft JSON anymore.
AddNewtonsoftJson in ConfigureServices seemingly does nothing, and the new Json serializer seems to work on properties only, not fields. It does not see the JSONIgnoreAttribute.
In ConfigureServices (in Startup) I have the line
services.AddMvc(x => x.EnableEndpointRouting = false).AddNewtonsoftJson();
which doesn't seem to be doing what it should. In my application, only properties are serialized, not fields, and the [JSONIgnore] attribute does nothing.
The lack of fields I can work around by promoting all the public fields I need to be properties, but I need to be able to ignore some.
Has anyone else had this? How do I either get the new JSON serializer to ignore some properties and serialize public fields, or go back to Newtonsoft?
System.Text.Json has a JsonIgnore attribute, please see How to ignore properties with System.Text.Json.
In order for it to work you will need to remove the dependency on Newtonsoft.Json and change the namespaces in relevant files to System.Text.Json.Serialization;
Sytem.Text.Json can include fields, but only public ones.
using System.Text.Json;
using System.Text.Json.Serialization;
var json = JsonSerializer.Serialize(new O(), new JsonSerializerOptions() { WriteIndented = true});
Console.WriteLine(json);
class O {
[JsonInclude]
public int publicField = 1;
//[JsonInclude]
//This won't work and throws an exception
//'The non-public property 'privateField' on type 'O' is annotated with 'JsonIncludeAttribute' which is invalid.'
private int privateField = 2;
[JsonIgnore]
public int P1 { get; set;} = 3;
public int P2 { get; set; } = 4;
}
This results in:
{
"P2": 4,
"publicField": 1
}
Alternatively you can use IncludeFields
var json = JsonSerializer.Serialize(new O(), new JsonSerializerOptions() { IncludeFields = true});
(Reference: Include fields)

What's the best way to properly consume one of two "successful" responses from the same endpoint on a web api?

I have C# client that's making requests to another Web API.
The Web API I'm consuming can respond with 2 "successful" responses, both 200's.
I need to intercept the response before I map it to its corresponding object and am looking for the best practice.
I own both the client and the server, so either can be adjusted to fit best practices.
I haven't tried much, but I've considered the following
Having an understood map of unclaimed status codes to each error.
Try-catching the attempt to map the JSON to either of the two business objects or otherwise parsing and comparing the JSON to the expected format each object expects before mapping it.
response = await PostAsync(
$"{BaseUrl}/endpoint/{identifier}",
new StringContent(jsonBody, Encoding.UTF8, "application/json")
);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = json.Deserialize<ResponseObject>(responseJson);
businessObject = new BusinessObject(responseObject);```
//These are two example responses
{
"StartDate": "01/01/01",
"EndDate": "01/01/01"
"Object": {
"Property1": 1,
"Property2": "someStr"
}
}
//OR
{
"message": "Valid reason you are not receiving the JSON above, status 200 btw"
}
A low-budget solution could be to simply assess the response body before deserializing.
var response = await PostAsync(
$"{BaseUrl}/endpoint/{identifier}",
new StringContent(jsonBody, Encoding.UTF8, "application/json")
);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObject = responseJson.Contains("[special string or property name]")
? json.Deserialize<ResponseObjectA>(responseJson)
: json.Deserialize<ResponseObjectB>(responseJson);
I recently had a similar issue but mine was when I was consuming messages from a queue. The way I resolved it was by telling Newtonsoft to add the type when serializing my object. You can do this like so:
JsonConvert.SerializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
With SerializeObject and DeserializeObject, you can pass some optional serialization settings. Here, we are passing a new instance of the JsonSerializerSettings to this parameter and settings it's TypeNameHandling to the enum value TypeNameHandling.All. This is telling Newtonsoft to embed the types of whatever it's serializing into the JSON itself. For instance, if you had a class that looked like this (based on your example JSON):
public class SuccessOne
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public SuccessOneChild Object { get; set; }
}
public class SuccessOneChild
{
public int Property1 { get; set; }
public string Property2 { get; set; }
}
Then the JSON you get will look like this:
{
"$type":"Functions.Tests.SuccessOne, Functions.Tests",
"StartDate":"2019-07-09T09:32:11.0090507+01:00",
"EndDate":"2019-07-16T09:32:11.0091048+01:00",
"Object":{
"$type":"Functions.Tests.SuccessOneChild, Functions.Tests",
"Property1":1,
"Property2":"someStr"
}
}
Notice the extra $type properties that have been added? These have been added automatically by Newtonsoft because you told it to in the serialization settings.
Then, when you come to deserialising, you can also tell it to use the type handling. It will look at the extra type property in your JSON and deserialize to whatever that type is. When doing this, you don't need to give DeserializeObject a type argument:
var response = JsonConvert.DeserializeObject(response, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Note that wherever you're deserializing will need to know about the classes. So it may be useful to have those classes in a shared library that both your API and consumer can reference to avoid duplicating your code.
You can then handle the response based on it's type. I did this by using a switch statement:
switch (response.GetType().Name)
{
case nameof(SuccessOne):
var successOneResponse = (SuccessOne)response;
handleSuccessOne(successOneResponse);
break;
case nameof(SuccessTwo):
var successTwoResponse = (SuccessTwo)response;
handleSuccessTwo(successTwoResponse);
break;
default:
// Throw exception or whatever you want to do
break;
}
I've also created a fiddle showing the serialization and deserialization here: https://dotnetfiddle.net/UcugGg

Newtonsoft JSON.NET Deserialization error

I am have a bunch of long json output in individual files. I need to read these files and deserialize them into the entities that originally generated the json (I have access to the original entities). Each file has the json output that was generated by serializing an object of type IEnumerable<Response<MyEntity>>.
I am getting an exception when attempting to deserialize, but I don't understand why. Here is my attempt to deserialize:
List<string> jsonOutputStrings;
// Read json from files and populate jsonOutputStrings list
List<Response<MyEntity>> apiResponses = new List<Response<MyEntity>>();
foreach (string json in jsonOutputStrings)
{
apiResponses.AddRange(JsonConvert.DeserializeObject<List<Response<MyEntity>>>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }).ToList());
}
I also tried deserializing to an IEnumerable instead of a list:
apiResponses.AddRange(JsonConvert.DeserializeObject<IEnumerable<Response<MyEntity>>>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }).ToList());
I get the following exception:
A first chance exception of type
'Newtonsoft.Json.JsonSerializationException' occurred in
Newtonsoft.Json.dll
Additional information: Cannot create and populate list type
System.Linq.Enumerable+WhereSelectListIterator`2[Entities.Requirement,Entities.RequirementEntity].
Path
'$values[0].ReturnedEntity.Summaries.$values[0].Requirements.$values',
line 1, position 715.
The entire json is too long to post (and also contains some confidential data) but I did find a place in the json that has this:
"Requirements":{"$id":"7","$type":"System.Linq.Enumerable+WhereSelectListIterator`2[[Entities.Requirement, Entities],[Entities.RequirementEntity, API.Entities]], System.Core","$values":[...]}
In the entity that I'm trying to deserialize into (same one that was originally serialized), Requirements is of type IEnumerable<Entities.RequirementEntity>.
It doesn't make sense to me how serialization from MyEntity works but deserializing to the same type doesn't. How do I solve this problem?
You must browse through Response<> and MyEntity and see how collections are initialized. This tells us that one of the collections in some class is created using Where method from linq. You can reproduce this error by executing this code:
class MyEntity
{
public MyEntity()
{
Data = new List<string>().Where(x => true);
}
public IEnumerable<string> Data { get; set; }
}
class Program
{
static void Main(string[] args)
{
string data = #"[{""Data"":[""a"",""b""]}]";
var j = JsonConvert.DeserializeObject<IEnumerable<MyEntity>>(data);
}
}
Another possibility is to have metadata in json. Then you have 2 solutions:
Set TypeNameHandling to TypeNameHandling.None (as somebody mentioned in comment);
Replace not working types in string with working ones
Using TypeNameHandling.None may lead to wrong deserialization for exmaple when you have IEnumerable<BaseType> and that list contains subtype of BaseType.
In that case you should go for second option. Basically you should replace any type that is not deserializing and replace it for example with List.
Sample code:
class MyEntity
{
public IEnumerable<string> Data { get; set; }
}
class Program
{
static void Main(string[] args)
{
IList<MyEntity> entities = new MyEntity[] {
new MyEntity { Data = new [] { "1", "2" }.Where(x => x != string.Empty) },
new MyEntity { Data = new [] { "A", "B" }.AsQueryable().Where(x => x != string.Empty) },
new MyEntity { Data = new List<string> { "A", "B" } },
};
string data = JsonConvert.SerializeObject(entities, Formatting.Indented, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
data = Regex.Replace(data, "\"\\$type\":\\s+\"System.Linq.Enumerable\\+WhereArrayIterator(.+?), System.Core\",", "\"$type\": \"System.Collections.Generic.List$1, mscorlib\",", RegexOptions.Singleline);
var j = JsonConvert.DeserializeObject<IEnumerable<MyEntity>>(data, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
}
}
Is there a Linq result in the serialized entity? Maybe defined as an IEnumerable and filled with a Linq Where result? It seems that's where your problem lies.
You should convert it to a List or Array before serializing.

Json.NET StringEnumConverter not working as expected

I'm attempting to use Json.NET with the System.Net.Http.HttpClient to send an object with an enum property, however the enum is always serialized as an integer value rather than the string equivalent.
I've tried following the instructions here:
http://james.newtonking.com/archive/2013/05/08/json-net-5-0-release-5-defaultsettings-and-extension-data
By both adding an instance of StringEnumConverter to the JsonSerializerSettings and also tried to decorate the enum property with [JsonProperty(ItemConverterType = typeof(StringEnumConverter))] neither of which appear to be working in my example.
I'm using Json.NET version 5.0.8
Can anyone tell me what I'm doing wrong please? Here is a sample console app to replicate showing both the global serializer settings and the decorated property:
Thanks.
using System;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
HttpClient client = new HttpClient { BaseAddress = new Uri("http://test-uri.com") };
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
return settings;
});
var data = new TestData { Enum = TestEnum.Hello };
// The following posts: {"Enum":0}
// Shouldn't it post {"Enum":"Hello"} instead?
var result = client.PostAsJsonAsync("/test", data).Result;
}
public class TestData
{
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public TestEnum Enum { get; set; }
}
public enum TestEnum
{
Hello,
World
}
}
}
I've inspected this with Fiddler and it posts: {"Enum":0} rather than {"Enum":"Hello"} which is what I would expect.
The ItemConverterType property of the JsonPropertyAttribute attribute is the converter to use for items of a collection. You should be using the JsonConverterAttribute attribute.
public class TestData
{
[JsonConverter(typeof(StringEnumConverter))]
public TestEnum Enum { get; set; }
}
I think I've found a way of getting it to work without decorating attributes. It involves replacing client.PostAsJsonAsync() with client.PostAsync(). You can then specify the MediaTypeFormatter to use which in this case will be the JsonMediaTypeFormatter.
Source here: .net HttpClient with custom JsonConverter
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
var formatter = new JsonMediaTypeFormatter { SerializerSettings = settings };
var response = client.PostAsync("/test", data, formatter).Result;
This still doesn't explain why the DefaultSettings aren't being applied. I can only assume that the presence of the [JsonConverter] property forces the HttpClient to use Json.NET for serialization, otherwise it just uses the default serializer.

Categories

Resources