Deserializing json to C# list with existing items - c#

Given the following classes:
class Report {
public Report() {
this.Fields=new List<Field>();
}
[JsonProperty("fields")]
public IList<Field> Fields { get; private set; }
}
class Field {
[JsonProperty("identifier")]
public Guid Identfier { get;set; }
[JsonProperty("name")]
public string Name { get;set; }
}
and the following test method set up:
var report = new Report();
report.Fields.Add(new Field { Identifier = new Guid("26a94eab-3d50-4330-8203-e7750abaa060"), Name = "Field 1" });
report.Fields.Add(new Field { Identifier = new Guid("852107db-b5d1-4344-9f71-7bd90b96fec0"), Name = "Field 2" });
var json = "{\"fields\":[{\"identifier\":\"852107db-b5d1-4344-9f71-7bd90b96fec0\",\"name\":\"name changed\"},{\"identifier\":\"ac424aff-22b5-4bf3-8232-031eb060f7c2\",\"name\":\"new field\"}]}";
JsonConvert.PopulateObject(json, report);
Assert.IsTrue(report.Fields.Count == 2, "The number of fields was incorrect.");
How do I get JSON.Net to know that the field with identifier "852107db-b5d1-4344-9f71-7bd90b96fec0" should apply to the existing field with the same identifier?
Also, is it possible to get JSON.Net to remove items that do not exist within the given JSON array, (specifically the field with identifier "26a94eab-3d50-4330-8203-e7750abaa060" should be removed because it does not exist in the given json array.
If there is a way to manually code or override the way that JSON analyses a list then that would be better because I could write the code to say "this is the item you need" or "use this newly created item" or just "don't do anything to this item because I have removed it". Anyone know of a way I can do this please?

You can use the option ObjectCreationHandling = ObjectCreationHandling.Replace.
You can do this for your entire data model using serializer settings, as is shown in Json.Net PopulateObject Appending list rather than setting value:
var serializerSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
JsonConvert.PopulateObject(json, report, serializerSettings);
Or, you can set the option on the JsonProperty attribute you are already using if you don't want to do this universally:
class Report
{
public Report()
{
this.Fields = new List<Field>();
}
[JsonProperty("fields", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public IList<Field> Fields { get; private set; }
}

Related

Why can't I deserialize string Into a model?

I'm using a HTTP client to get a string and picking out my json from that and converting back to a string to deserialize it into a List of "Spots" but can't get it to to work
I've tried changing the DeserializeObject type to every mix of "List, IList, HardwareUpdateSpot, HardWareModel" and still it didn't work
public async Task<IList<HardwareUpdateSpot>> UpdateSpotHTTP()
{
var client = new HttpClient();
var response = await client.GetAsync(
"https://io.adafruit.com/api/v2/Corey673/feeds/673d855c-9f66-4e49-8b2c-737e829d880c");
var responseHTTP = response.Content.ReadAsStringAsync();
var j = JObject.Parse(responseHTTP.Result);
var b = j.GetValue("last_value");
var h = b.ToString();
var dataObjects = JsonConvert.DeserializeObject<IList<HardwareUpdateSpot>>(h);
return null;
}
public record HardWareModel
{
public int SpotId { get; set; }
public string Occupied { get; set; }
}
public class HardwareUpdateSpot
{
public IList<HardWareModel> Spots { get; set; }
public HardwareUpdateSpot(IList<HardWareModel> spots)
{
Spots = spots;
}
}
While trying to reproduce your problem I have examined the returned value from the API call. This is the json returned:
{"Spot":[
{"SpotId":"1","Occupied":"false",},
{"SpotId":"2","Occupied":"false",},
{"SpotId":"3","Occupied":"false",},
{"SpotId":"4","Occupied":"false"}
]}
So, it easy to see that the returned json requires a root object with a public Spot property (not Spots) and this property should be a collection.
Instead the code above expects a json that has at the root level a collection of HardwareUpdateSpot and of course it cannot work.
To fix the problem you need to change the deserialization to:
JsonConvert.DeserializeObject<HardwareUpdateSpot>(h);
Now, you need to make some changes to the HardwareUpdateSpot class to make it compatible with the json.
First you need to add a parameterless constructor required by jsonconvert, then you need to fix the difference between the name for the property (Spots) and the name returned (Spot).
So you can change the property name to match the json or add the attribute that make Spots=Spot
[JsonProperty("Spot")]
public IList<HardWareModel> Spots { get; set; }

Adding backward compatibility support for an older JSON structure

I have developed an app for android which stores a serialized domain model in a JSON file to the local storage. Now the thing is, sometimes I make changes to the domain model (new features) and want to have the option to easily load a previous structure of the JSON file from the local storage. How can I do this?
I thought of deserializing the object anonymously and using auto-mapper, but I want to hear others' ideas first before going this path.
If a code example of the domain model is needed (before and after), I'll provide. Thanks everyone.
How you support backward compatibility depends on how different your "before" and "after" models are going to be.
If you are just going to be adding new properties, then this should not pose a problem at all; you can just deserialize the old JSON into the new model and it will work just fine without errors.
If you are replacing obsolete properties with different properties, you can use techniques described in Making a property deserialize but not serialize with json.net to migrate old properties to new.
If you are making big structural changes, then you may want to use different classes for each version. When you serialize the models, ensure that a Version property (or some other reliable marker) is written into the JSON. Then when it is time to deserialize, you can load the JSON into a JToken, inspect the Version property and then populate the appropriate model for the version from the JToken. If you want, you can encapsulate this logic into a JsonConverter class.
Let's walk through some examples. Say we are writing an application which keeps some information about people. We'll start with the simplest possible model: a Person class which has a single property for the person's name.
public class Person // Version 1
{
public string Name { get; set; }
}
Let's create a "database" of people (I'll just use a simple list here) and serialize it.
List<Person> people = new List<Person>
{
new Person { Name = "Joe Schmoe" }
};
string json = JsonConvert.SerializeObject(people);
Console.WriteLine(json);
That gives us the following JSON.
[{"Name":"Joe Schmoe"}]
Fiddle: https://dotnetfiddle.net/NTOnu2
OK, now say we want to enhance the application to keep track of people's birthdays. This will not be a problem for backward compatibility because we're just going to be adding a new property; it won't affect the existing data in any way. Here's what the Person class looks like with the new property:
public class Person // Version 2
{
public string Name { get; set; }
public DateTime? Birthday { get; set; }
}
To test it, we can deserialize the Version 1 data into this new model, then add a new person to the list and serialize the model back to JSON. (I'll also add a formatting option to make the JSON easier to read.)
List<Person> people = JsonConvert.DeserializeObject<List<Person>>(json);
people.Add(new Person { Name = "Jane Doe", Birthday = new DateTime(1988, 10, 6) });
json = JsonConvert.SerializeObject(people, Formatting.Indented);
Console.WriteLine(json);
Everything works great. Here's what the JSON looks like now:
[
{
"Name": "Joe Schmoe",
"Birthday": null
},
{
"Name": "Jane Doe",
"Birthday": "1988-10-06T00:00:00"
}
]
Fiddle: https://dotnetfiddle.net/pftGav
Alright, now let's say we've realized that just using a single Name property isn't robust enough. It would be better if we had separate FirstName and LastName properties instead. That way we can do things like sort the names in directory order (last, first) and print informal greetings like "Hi, Joe!".
Fortunately, we know that the data has been reliably entered so far with the first name preceding the last name and a space between them, so we have a viable upgrade path: we can split the Name property on the space and fill the two new properties from it. After we do that, we want to treat the Name property as obsolete; we don't want it written back to the JSON in the future.
Let's make some changes to our model to accomplish these goals. After adding the two new string properties FirstName and LastName, we need to change the old Name property as follows:
Make its set method set the FirstName and LastName properties as explained above;
Remove its get method so that the Name property does not get written to JSON;
Make it private so it is no longer part of the public interface of Person;
Add a [JsonProperty] attribute so that Json.Net can still "see" it even though it is private.
And of course, we'll have to update any other code that uses the Name property to use the new properties instead. Here is what our Person class looks like now:
public class Person // Version 3
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? Birthday { get; set; }
// This property is here to support transitioning from Version 2 to Version 3
[JsonProperty]
private string Name
{
set
{
if (value != null)
{
string[] parts = value.Trim().Split(' ');
if (parts.Length > 0) FirstName = parts[0];
if (parts.Length > 1) LastName = parts[1];
}
}
}
}
To demonstrate that everything works, let's load our Version 2 JSON into this model, sort the people by last name and then reserialize it to JSON:
List<Person> people = JsonConvert.DeserializeObject<List<Person>>(json);
people = people.OrderBy(p => p.LastName).ThenBy(p => p.FirstName).ToList();
json = JsonConvert.SerializeObject(people, Formatting.Indented);
Console.WriteLine(json);
Looks good! Here is the result:
[
{
"FirstName": "Jane",
"LastName": "Doe",
"Birthday": "1988-10-06T00:00:00"
},
{
"FirstName": "Joe",
"LastName": "Schmoe",
"Birthday": null
}
]
Fiddle: https://dotnetfiddle.net/T8NXMM
Now for the big one. Let's say we want add a new feature to keep track of each person's home address. But the kicker is, people can share the same address, and we don't want duplicate data in that case. This requires a big change to our data model, because up until now it's just been a list of people. Now we need a second list for the addresses, and we need a way to tie the people to the addresses. And of course we still want to support reading all the old data formats. How can we do this?
First let's create the new classes we will need. We need an Address class of course:
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
We can reuse the same Person class; the only change we need is to add an AddressId property to link each person to an address.
public class Person
{
public int? AddressId { get; set; }
...
}
Lastly, we need a new class at the root level to hold the lists of people and addresses. Let's also give it a Version property in case we need to make changes to the data model in the future:
public class RootModel
{
public string Version { get { return "4"; } }
public List<Person> People { get; set; }
public List<Address> Addresses { get; set; }
}
That's it for the model; now the big issue is how do we handle the differing JSON? In versions 3 and earlier, the JSON was an array of objects. But with this new model, the JSON will be an object containing two arrays.
The solution is to use a custom JsonConverter for the new model. We can read the JSON into a JToken and then populate the new model differently depending on what we find (array vs. object). If we get an object, we'll check for the new version number property we just added to the model.
Here is the code for the converter:
public class RootModelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(RootModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
RootModel model = new RootModel();
if (token.Type == JTokenType.Array)
{
// we have a Version 3 or earlier model, which is just a list of people.
model.People = token.ToObject<List<Person>>(serializer);
model.Addresses = new List<Address>();
return model;
}
else if (token.Type == JTokenType.Object)
{
// Check that the version is something we are expecting
string version = (string)token["Version"];
if (version == "4")
{
// all good, so populate the current model
serializer.Populate(token.CreateReader(), model);
return model;
}
else
{
throw new JsonException("Unexpected version: " + version);
}
}
else
{
throw new JsonException("Unexpected token: " + token.Type);
}
}
// This signals that we just want to use the default serialization for writing
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, we create an instance and pass it to the DeserializeObject method like this:
RootModelConverter converter = new RootModelConverter();
RootModel model = JsonConvert.DeserializeObject<RootModel>(json, converter);
Now that we have the model loaded, we can update the data to show that Joe and Jane live at the same address and serialize it back out again:
model.Addresses.Add(new Address
{
Id = 1,
Street = "123 Main Street",
City = "Birmingham",
State = "AL",
PostalCode = "35201",
Country = "USA"
});
foreach (var person in model.People)
{
person.AddressId = 1;
}
json = JsonConvert.SerializeObject(model, Formatting.Indented);
Console.WriteLine(json);
Here is the resulting JSON:
{
"Version": 4,
"People": [
{
"FirstName": "Jane",
"LastName": "Doe",
"Birthday": "1988-10-06T00:00:00",
"AddressId": 1
},
{
"FirstName": "Joe",
"LastName": "Schmoe",
"Birthday": null,
"AddressId": 1
}
],
"Addresses": [
{
"Id": 1,
"Street": "123 Main Street",
"City": "Birmingham",
"State": "AL",
"PostalCode": "35201",
"Country": "USA"
}
]
}
We can confirm the converter works with the new Version 4 JSON format as well by deserializing it again and dumping out some of the data:
model = JsonConvert.DeserializeObject<RootModel>(json, converter);
foreach (var person in model.People)
{
Address addr = model.Addresses.FirstOrDefault(a => a.Id == person.AddressId);
Console.Write(person.FirstName + " " + person.LastName);
Console.WriteLine(addr != null ? " lives in " + addr.City + ", " + addr.State : "");
}
Output:
Jane Doe lives in Birmingham, AL
Joe Schmoe lives in Birmingham, AL
Fiddle: https://dotnetfiddle.net/4lcDvE

NEST 2.0 doesn't persist some fields into ElasticSearch 2.0

This is my document:
[ElasticsearchType(Name = "MyDoc")]
public class MyDoc: Dictionary<string, object>
{
[String(Store = false, Index = FieldIndexOption.NotAnalyzed)]
public string text { get; set; }
}
As you can see, it inherits from Dictionary<string, object> so I can dinamically add fields to it (this is a requirement to make aggregation work)
Here I store the mapping:
client.Map<MyDoc>(m => m.Index("myindexname").AutoMap());
Now I create a new record and store it:
var rec= new MyDoc();
rec.Add("id", "mystuff");
rec.text = "mytext";
client.Index(rec, i => i.Index("myindexname"));
client.Refresh("myindexname");
The record get actually stored but the text field is not persisted. Here the JSON back from ElasticSearch 2.0
{
"_index": "myindexname",
"_type": "MyDoc",
"_id": "AVM3B2dlrjN2fcJKmw_z",
"_version": 1,
"_score": 1,
"_source": {
"id": "mystuff"
}
}
If I remove the base class from MyDoc the text field is stored correctly but obviously the dictionary content is not (I also need to remove the .Add() bit as the document doesn't inherit from a Dictionary).
How to store both the text field and the dictionary content?
Sorry i wrote wrong suggestion in my previous post so i deleted it.
I think issues is actually in serialization since your base class is Dictionary
I would do two things first try to serialize your object to see output string i am pretty sure that text is ignored.
Second i would change class to following
public class MyDoc : Dictionary<string, object>
{
public string text
{
get
{
object mytext;
return TryGetValue("text", out mytext) ? mytext.ToString() : null;
}
set { this.Add("text", value);}
}
}
PS. As i thought issue is on c# side since you inherit from dictionary,
var rec = new MyDoc();
rec.Add("id", "mystuff");
rec.text = "mytext";
//Text2 is property public string text2 { get; set; }
rec.text2 = "mytext2";
var test = JsonConvert.SerializeObject(rec); //{"id":"mystuff","text":"mytext"}

Json.NET does not recognize Data Annotations and allows the data through

I have the following code:
public class EventController : ApiController
{
//public IHttpActionResult Post(List<Event> Events)
public IHttpActionResult Post(Newtonsoft.Json.Linq.JArray J)
{
//Debug.WriteLine(J.ToString());
List<Event> Events = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Event>>(J.ToString(), new Newtonsoft.Json.JsonSerializerSettings {
Error = delegate(object sender, ErrorEventArgs args) {
Debug.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = { new IsoDateTimeConverter() }
}
);
foreach (Event Event in Events)
{
Debug.WriteLine(Event.Importance.ToString());
Debug.WriteLine(Event.Date.ToString());
Debug.WriteLine(Event.Description);
}
}
}
public class Event
{
[DataAnnotationsExtensions.Integer(ErrorMessage = "{0} must be a number.")]
[Range(0,10),Required]
public Int32 Importance { get; set; }
//[OnConversionError: "Please enter a valid date."]
[Required]
[DataAnnotationsExtensions.Date]
public object Date { get; set; }
[RegularExpression(#"^.{20,100}$", ErrorMessage="{0} must be between 20 and 100 characters.")]
[Required]
public string Description { get; set; }
}
I'm posting:
[{"Importancee":"adasdasd","Date":"2005-10-32","Descriptione":""},
{"Importance":"6.0","Date":"2015-10-02","Description":"a"}]
"Importance" is misspelled on purpose to simulate the scenario of missing data. When I post this I expect the delegate function to capture the invalid data and let me know the required fields are missing. I also expect the Regular Expression used for Description to cause an error for the 1 character description "a". Instead Json.net's Deserializer skips the missing fields and sets those properties to null and it sets the 2nd Description property to the "a" string. It's completely ignoring the Data Annotations. Is there any way to get Json.NET to recognize the Annotations?
You could generate a JSchema from the Data Annotation attributes:
http://www.newtonsoft.com/jsonschema/help/html/GenerateWithDataAnnotations.htm
And validate them them using:
http://www.newtonsoft.com/json/help/html/JsonSchema.htm
Data Annotations will not work directly, but with little effort, I believe you can get what you need.

How to ignore a property in class if null, using json.net

I am using Json.NET to serialize a class to JSON.
I have the class like this:
class Test1
{
[JsonProperty("id")]
public string ID { get; set; }
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("url")]
public string URL { get; set; }
[JsonProperty("item")]
public List<Test2> Test2List { get; set; }
}
I want to add a JsonIgnore() attribute to Test2List property only when Test2List is null. If it is not null then I want to include it in my json.
An alternate solution using the JsonProperty attribute:
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
// or
[JsonProperty("property_name", NullValueHandling=NullValueHandling.Ignore)]
// or for all properties in a class
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
As seen in this online doc.
As per James Newton King: If you create the serializer yourself rather than using JavaScriptConvert there is a NullValueHandling property which you can set to ignore.
Here's a sample:
JsonSerializer _jsonWriter = new JsonSerializer {
NullValueHandling = NullValueHandling.Ignore
};
Alternatively, as suggested by #amit
JsonConvert.SerializeObject(myObject,
Newtonsoft.Json.Formatting.None,
new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore
});
JSON.NET also respects the EmitDefaultValue property on DataMemberAttribute, in case you don't want to add Newtonsoft-specific attributes to your model:
[DataMember(Name="property_name", EmitDefaultValue=false)]
You can write: [JsonProperty("property_name",DefaultValueHandling = DefaultValueHandling.Ignore)]
It also takes care of not serializing properties with default values (not only null). It can be useful for enums for example.
You can do this to ignore all nulls in an object you're serializing, and any null properties won't then appear in the JSON
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;
var myJson = JsonConvert.SerializeObject(myObject, settings);
In my case, using .NET 6 this was the solution:
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
More info here.
As can be seen in this link on their site (http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx) I support using [Default()] to specify default values
Taken from the link
public class Invoice
{
public string Company { get; set; }
public decimal Amount { get; set; }
// false is default value of bool
public bool Paid { get; set; }
// null is default value of nullable
public DateTime? PaidDate { get; set; }
// customize default values
[DefaultValue(30)]
public int FollowUpDays { get; set; }
[DefaultValue("")]
public string FollowUpEmailAddress { get; set; }
}
Invoice invoice = new Invoice
{
Company = "Acme Ltd.",
Amount = 50.0m,
Paid = false,
FollowUpDays = 30,
FollowUpEmailAddress = string.Empty,
PaidDate = null
};
string included = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0,
// "Paid": false,
// "PaidDate": null,
// "FollowUpDays": 30,
// "FollowUpEmailAddress": ""
// }
string ignored = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0
// }
In .Net Core this is much easier now. In your startup.cs just add json options and you can configure the settings there.
public void ConfigureServices(IServiceCollection services)
....
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
With Json.NET
public class Movie
{
public string Name { get; set; }
public string Description { get; set; }
public string Classification { get; set; }
public string Studio { get; set; }
public DateTime? ReleaseDate { get; set; }
public List<string> ReleaseCountries { get; set; }
}
Movie movie = new Movie();
movie.Name = "Bad Boys III";
movie.Description = "It's no Bad Boys";
string ignored = JsonConvert.SerializeObject(movie,
Formatting.Indented,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
The result will be:
{
"Name": "Bad Boys III",
"Description": "It's no Bad Boys"
}
With System.Text.Json and .NET Core 3.0 this worked for me:
var jsonSerializerOptions = new JsonSerializerOptions()
{
IgnoreNullValues = true
};
var myJson = JsonSerializer.Serialize(myObject, jsonSerializerOptions );
An adaption to #Mrchief's / #amit's answer, but for people using VB
Dim JSONOut As String = JsonConvert.SerializeObject(
myContainerObject,
New JsonSerializerSettings With {
.NullValueHandling = NullValueHandling.Ignore
}
)
See:
"Object Initializers: Named and Anonymous Types (Visual Basic)"
https://msdn.microsoft.com/en-us/library/bb385125.aspx
Or just by setting like this.
services.AddMvc().AddJsonOptions(options =>
options.JsonSerializerOptions.IgnoreNullValues = true;
});
To expound slightly on GlennG's very helpful answer (translating the syntax from C# to VB.Net is not always "obvious") you can also decorate individual class properties to manage how null values are handled. If you do this don't use the global JsonSerializerSettings from GlennG's suggestion, otherwise it will override the individual decorations. This comes in handy if you want a null item to appear in the JSON so the consumer doesn't have to do any special handling. If, for example, the consumer needs to know an array of optional items is normally available, but is currently empty...
The decoration in the property declaration looks like this:
<JsonPropertyAttribute("MyProperty", DefaultValueHandling:=NullValueHandling.Include)> Public Property MyProperty As New List(of String)
For those properties you don't want to have appear at all in the JSON change :=NullValueHandling.Include to :=NullValueHandling.Ignore.
By the way - I've found that you can decorate a property for both XML and JSON serialization just fine (just put them right next to each other). This gives me the option to call the XML serializer in dotnet or the NewtonSoft serializer at will - both work side-by-side and my customers have the option to work with XML or JSON. This is slick as snot on a doorknob since I have customers that require both!
Here's an option that's similar, but provides another choice:
public class DefaultJsonSerializer : JsonSerializerSettings
{
public DefaultJsonSerializer()
{
NullValueHandling = NullValueHandling.Ignore;
}
}
Then, I use it like this:
JsonConvert.SerializeObject(postObj, new DefaultJsonSerializer());
The difference here is that:
Reduces repeated code by instantiating and configuring JsonSerializerSettings each place it's used.
Saves time in configuring every property of every object to be serialized.
Still gives other developers flexibility in serialization options, rather than having the property explicitly specified on a reusable object.
My use-case is that the code is a 3rd party library and I don't want to force serialization options on developers who would want to reuse my classes.
Potential drawbacks are that it's another object that other developers would need to know about, or if your application is small and this approach wouldn't matter for a single serialization.
This does not exactly answer the original question, but may prove useful depending on the use case. (And since I wound up here after my search, it may be useful for others.)
In my most recent experience, I'm working with a PATCH api. If a property is specified but with no value given (null/undefined because it's js), then the property and value are removed from the object being patched. So I was looking for a way to selectively build an object that could be serialized in such a way that this would work.
I remembered seeing the ExpandoObject, but never had a true use case for it until today. This allows you to build an object dynamically, so you won't have null properties unless you want them there.
Here is a working fiddle, with the code below.
Results:
Standard class serialization
noName: {"Name":null,"Company":"Acme"}
noCompany: {"Name":"Fred Foo","Company":null}
defaultEmpty: {"Name":null,"Company":null}
ExpandoObject serialization
noName: {"Company":"Acme"}
noCompany: {"name":"Fred Foo"}
defaultEmpty: {}
Code:
using Newtonsoft.Json;
using System;
using System.Dynamic;
public class Program
{
public static void Main()
{
SampleObject noName = new SampleObject() { Company = "Acme" };
SampleObject noCompany = new SampleObject() { Name = "Fred Foo" };
SampleObject defaultEmpty = new SampleObject();
Console.WriteLine("Standard class serialization");
Console.WriteLine($" noName: { JsonConvert.SerializeObject(noName) }");
Console.WriteLine($" noCompany: { JsonConvert.SerializeObject(noCompany) }");
Console.WriteLine($" defaultEmpty: { JsonConvert.SerializeObject(defaultEmpty) }");
Console.WriteLine("ExpandoObject serialization");
Console.WriteLine($" noName: { JsonConvert.SerializeObject(noName.CreateDynamicForPatch()) }");
Console.WriteLine($" noCompany: { JsonConvert.SerializeObject(noCompany.CreateDynamicForPatch()) }");
Console.WriteLine($" defaultEmpty: { JsonConvert.SerializeObject(defaultEmpty.CreateDynamicForPatch()) }");
}
}
public class SampleObject {
public string Name { get; set; }
public string Company { get; set; }
public object CreateDynamicForPatch()
{
dynamic x = new ExpandoObject();
if (!string.IsNullOrWhiteSpace(Name))
{
x.name = Name;
}
if (!string.IsNullOrEmpty(Company))
{
x.Company = Company;
}
return x;
}
}
.Net 6 -
Add the code in Program.cs. This will ignore the class or record property if it is null.
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(opts =>
{
var enumConverter = new JsonStringEnumConverter();
opts.JsonSerializerOptions.Converters.Add(enumConverter);
opts.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull;
});
var settings = new JsonSerializerSettings();
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
//you can add multiple settings and then use it
var bodyAsJson = JsonConvert.SerializeObject(body, Formatting.Indented, settings);

Categories