Reputation: 675
I am working with Post endpoint in C# and I want to make all fields optional, but also want to update it to null/empty list. I know this is very weird requirement. I can set int and string data types to -1 or some default strings, but I am finding difficulties for List or any list of data.
I can put any extra flag for conditional update, but this is increasing number of properties in request body.
Could anyone suggest me solution for this case, if anyone has done similar problem. I know solution will be going to tricky one.
Upvotes: 0
Views: 1345
Reputation: 435
Sorry for the delay.
Let me try to explain it with more code.
First of all, we do a generic method that will fill a generic object with data extracted from a JSON string.
public static void FillObjectFromJson<T>(T objectToFill, string objectInJson)
where T : class
{
// Check parameters
if (objectToFill == null) throw new ArgumentNullException(nameof(objectToFill));
if (string.IsNullOrEmpty(objectInJson)) throw new ArgumentNullException(nameof(objectInJson));
// Deserialize in a typed object (helpful for the type converter)
var typed = JsonConvert.DeserializeObject<T>(objectInJson);
// Deserialize in an expando object (for check properties)
var expando = JsonConvert.DeserializeObject<ExpandoObject>(objectInJson);
// Converts the expando to dictionary for check all given properties
var dictionary = (IDictionary<string, object>)expando;
// Read all properties of the given object (the only one you can assign)
foreach (var property in typeof(T).GetProperties().Where(x => x.CanWrite))
{
// If dictionary contains the property, it was in the JSON string,
// so we have to replace it
if (dictionary.ContainsKey(property.Name))
{
var propValue = property.GetValue(typed);
property.SetValue(objectToFill, propValue);
}
}
}
For the performance it's not the best, since we deserialize the same JSON twice:
I did it because we can have some problems with property types. In this example, in the ExpandoObject the Age property will be in of type int64 instead of int32. So, if we want to deserialize the JSON only once, then we need to cast properties. Feel free to change this code as you prefer.
Now that we have a generic method, try to use it in an example.
First of all, we need a class to use:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// This method is used only for this example, just for write data to the console
public override string ToString()
{
return $"Name: {Name} - Age: {Age}";
}
}
Then we need a method that will extract the data to update from storage. In this example we will simply back an object:
public static Person ReadPersonFromStorage()
{
return new Person
{
Name = "Name from storage",
Age = 44
};
}
Finally, we can write our test method:
// Example of JSON with explicit name (so we have to set it as null)
var personWithNameJson = "{ \"Name\" : null, \"Age\" : 24 }";
// Read the original value from the storage
var person = ReadPersonFromStorage();
Console.WriteLine(person); // We have the name
// Fills from JSON (it will replace the Name since it's in the JSON)
FillObjectFromJson(person, personWithNameJson);
Console.WriteLine(person); // The name is set to null
// Example of JSON without explicit name (so you have to leave it with the original value)
var personWithoutNameJson = "{ \"Age\" : 24 }";
// Read the original value from the storage
var otherPerson = ReadPersonFromStorage();
Console.WriteLine(otherPerson); // We have the name
// Fills from JSON (it won't replace the Name since it's NOT in the JSON)
FillObjectFromJson(otherPerson, personWithoutNameJson);
Console.WriteLine(otherPerson); // We still have the name since it's not in the JSON
I hope it's more clear what I mean and, even better, that i would help you.
PS: For this sample I used Newtonsoft for the JSON deserialization.
Upvotes: 1
Reputation: 435
If you're in control of both client and server, you can manage it using the format of the request.
Suppose you have an object like this:
public class Customer {
public string Name { get; set; }
public int? Age { get; set; }
}
Then you can manage two kind of request. I will explain it using JSON format.
{
"Name": null,
"Age": 12
}
In this case, the request contains the value "Name", so you will set it to null in the database (or any other storage)
{
"Age": 12
}
In this case, the request does not contain the value "Name", so ti means it hasn't to be changed.
For the JSON format, both request are the same (Name = null). But in your code they will work in different way.
Of course:
Upvotes: 0