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
Related
I am trying to make a multi-subject quiz game for children using unity3D and questions and answers are stored in a json file. Now i want to create an object named "science" and "maths" and want to store their respective questions in them and when i want to access science i could loop and find and just store the science question in my string instead of reading the whole json file.
here is my json file.
Science ={
"CourseName":"Science",
"No_Of_Ques":4,
"Ques_Data":[
{ "Quesion":"which is the biggest planet in the solar system?",
"Answer":"jupiter",
"options":["mars","earth","venus","jupiter"]
},
{ "Quesion":"How many planets are there in solar system?",
"Answer":"Eight",
"options":["Seven","Nine","Five","Eight"]
},
{ "Quesion":"which is the closest planet to the sun?",
"Answer":"mercury",
"options":["mars","saturn","venus","mercury"]
},
{ "Quesion":"How many moons does jupiter have?",
"Answer":"12",
"options":["5","13","9","12"]
}
]
}
and this is how i have been acessing it so far
path = Application.dataPath + "/QnA.json";
string json = File.ReadAllText(path);
Course c1 = JsonUtility.FromJson<Course>(json);
return c1;
Course and needed serializable Classes:
[Serializable] public class Course
{
public string CourseName;
public string No_Of_Ques;
public QnA[] Ques_Data;
}
[Serializable]
public class QnA
{
public string Quesion;
public string Answer;
public string[] options;
}
i have tried so many things like Deserialization and Jobject asset but none of them seem to work and every implementation that i have found on the internet has the json data in the same file as the c# code but i can not do that as my json contains hundreds of lines of data. kindly help me out a little.
Create a course class in which create getter and setter functions for all of your json keys, for example:
if your json file is like that:
[
{
"CourseName": "Science",
"No_Of_Ques": 1,
...
},
{
"CourseName": "Math",
"No_Of_Ques": 1,
...
}
]
then course class should be:
public class Course
{
public string CourseName { get; set; }
public int No_Of_Ques { get; set; }
}
In your main class or anywhere you can access your selected course, here i am using only 0 index of a json, you can also loop through whole json and find your desirable course.
StreamReader to read a file
convert it to json
Deserialize the json as per your course
Console it
using (StreamReader r = new StreamReader("../../../js.json"))
{
string json = r.ReadToEnd();
List<Course> ro = JsonSerializer.Deserialize<List<Course>>(json);
Console.WriteLine(ro[0].CourseName);
}
I added json file in the same dire where my mainClass file is, as StreamReader requires an absolute path therefore I used an absoulte path for my json file.
using (StreamReader r = new StreamReader("../../../js.json"))
Require Libs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
Note: I created a console app, not sure your app type
If you want to store multiple courses in your file you will need to store them as json array (as you do with questions):
[
{
"CourseName":"Science",
"No_Of_Ques":1,
"Ques_Data":[
{
"Question":"which is the biggest planet in the solar system?",
"Answer":"jupiter",
"options":[
"mars",
"jupiter"
]
}
]
},
{
"CourseName":"Math",
"No_Of_Ques":1,
"Ques_Data":[
{
"Question":"2 + 2",
"Answer":"4",
"options":[
"4",
"0"
]
}
]
}
]
then you can deserialize them with next structure(for example):
public class Course
{
[JsonProperty("CourseName")]
public string CourseName { get; set; }
[JsonProperty("No_Of_Ques")]
public long NoOfQues { get; set; }
[JsonProperty("Ques_Data")]
public QuesDatum[] QuesData { get; set; }
}
public class QuesDatum
{
[JsonProperty("Question")]
public string Question{ get; set; }
[JsonProperty("Answer")]
public string Answer { get; set; }
[JsonProperty("options")]
public string[] Options { get; set; }
}
var courses = JsonConvert.DeserializeObject<List<Course>>(jsonString);
var course = courses.Where(...).FirstOrDefault();
Or try to use json path:
var course = JToken.Parse(jsonString)
.SelectToken("$[?(#.CourseName == 'Math')]")
.ToObject<Course>();
As for jsonString you can obtain it in any way, reading from file for example.
P.S.
There was typo "Quesion" -> "Question"
To serialize and deserialize objects you have to create a C# class (in your case should be Course class) that can be [Serializable].
First your Json should be a valid one, which it is, you can validate it here.
To serialize and deserialize you can use JsonUtility to certain point, cause it doesn't deserialize jagged arrays, complex objects etc. I recommend to use third party softwares like Newtonsoft or implement your own serialization/deserialization method for your way.
Edit:
Your JSON file should be without the "Science=" part, should look like:
{
"CourseName":"Science",
"No_Of_Ques":4,
"Ques_Data":[
{ "Quesion":"which is the biggest planet in the solar system?",
"Answer":"jupiter",
"options":["mars","earth","venus","jupiter"]
},
{ "Quesion":"How many planets are there in solar system?",
"Answer":"Eight",
"options":["Seven","Nine","Five","Eight"]
},
{ "Quesion":"which is the closest planet to the sun?",
"Answer":"mercury",
"options":["mars","saturn","venus","mercury"]
},
{ "Quesion":"How many moons does jupiter have?",
"Answer":"12",
"options":["5","13","9","12"]
}
]
}
Edit:
For your comment I think you got a misunderstood of how to handle the relation between files and variables.
You want to have (or at least it seems like this) one file for every type of course, so in this case, the text above will be your Science.json file.
When you store that information, you will do similar of what you do:
path = Application.dataPath + "/QnA.json";
string json = File.ReadAllText(path);
Course scienceCourse = JsonUtility.FromJson<Course>(json); //notice the name of the variable!
So as you can see for the variable name, you will read EVERY SINGLE JSON for every course.
The other way to do that, is to store all the courses on the same Json file, and then get them as an ARRAY of Courses -> Course[] allCourses
Using Science={...} to define your object is where you get confused about object definitions. It is not a Science object. It is a Course object.
It should be more like
{
"Courses" :
[
{
"CourseName" : "Science",
...
},
{
"CourseName" : "Maths",
...
}
]
}`.
Wrap it with:
"Quiz" :
{
"Courses" :
[
{
"CourseName" : "Science",
...
},
{
"CourseName" : "Maths",
...
}
]
}
and use
[Serializable]
public class Quiz
{
public Course[] courses;
}
To hold it as a C# object.
From here you can access your courses by quiz.Courses[0].Questions[17] or write helper methods in Quiz class to call courses by enums like quiz.GetCourse(CourseCategory.Science).Questions[8].
I also suggest using Questions instead of Question_Data. That is more object friendly and helps you semantically.
As an additional suggestion, instead of dumping all of the quiz in a single JSON, you may consider sending a single Course object depending on the course, requested using a query like http://myquizserver.com/quiz.php?course=science. Since you mentioned hundreds of lines of JSON, you may also consider getting data question by question.
I'm consuming some simple stock data in the form of JSON and plotting it on a chart. All works fine, except that some entries return NULL values because at that particular minute in time no trades were taken and therefore no price data is available. This creates gaps on the chart line.
So if the "close" value is null, I want to exclude the entire block including the "minute" and "volume" from being added into the ObservableCollection, and just move on to include the next one whose values are not null. Example JSON:
{
"minute": "10:21",
"close": null,
"volume": 0,
},{
"minute": "10:22",
"close": 47.56,
"volume": 6,
}
I have created a jsonSettings property which I have seen people talk about and claim work, yet it is not working. The code looks like this:
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
string content = await _client.GetStringAsync(url);
var json_Data = JsonConvert.DeserializeObject<ObservableCollection<ChartData>>(content,jsonSettings);
viewModel.LineData = json_Data;
And here are my models:
public class ChartData
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string minute { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public double? close { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int volume { get; set; }
}
public class ViewModel
{
public ObservableCollection<ChartData> LineData { get; set; }
public ViewModel()
{
LineData = new ObservableCollection<ChartData>();
}
}
I have tried numerous similar examples posted here and there and yet the null-value entries remain within the json_Data. Any ideas how to make it work?
Thanks!
NullValueHandling.Ignore will ignore null values for the relevant property of your model when serializing.
When deserialzing, you might consider deserializing to an IEnumerable<ChartData> then using Linq to filter out the objects you don't want, based on what is, after all, custom logic: "exclude objects that have close == null".
E.g. (untested air code):
var data = JsonConvert.DeserializeObject<IEnumerable<ChartData>>(content,jsonSettings)
.Where(cd => cd.close != null)
;
var observableData = new ObservableCollection<ChartData>(data);
I believe the serialize settings are more applicable when you're "serializing", and you want to not generate JSON for properties of a class when the value is null. In this case you're deserializing, so the JSON is as it is.
Regardless, if you need to exclude this entire object because "close" is null, it doesn't matter if the property is excluded or not, you still need to check for it. What I would do as #Jason was eluding to, would be to filter separately. Something like this:
JArray json = JArray.Parse(#"
[{
""minute"": ""10:21"",
""close"": null,
""volume"": 0,
},{
""minute"": ""10:22"",
""close"": 47.56,
""volume"": 6,
}]
");
var filteredJson = json.Where(j => j["close"].Value<double?>() != null);
I currently have a Web API that implements a RESTFul API. The model for my API looks like this:
public class Member
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Created { get; set; }
public DateTime BirthDate { get; set; }
public bool IsDeleted { get; set; }
}
I've implemented a PUT method for updating a row similar to this (for brevity, I've omitted some non-relevant stuff):
[Route("{id}")]
[HttpPut]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
// Do some error checking
// ...
// ...
var myDatabaseEntity = new BusinessLayer.Member(id);
myDatabaseEntity.FirstName = model.FirstName;
myDatabaseEntity.LastName = model.LastName;
myDatabaseEntity.Created = model.Created;
myDatabaseEntity.BirthDate = model.BirthDate;
myDatabaseEntity.IsDeleted = model.IsDeleted;
await myDatabaseEntity.SaveAsync();
}
Using PostMan, I can send the following JSON and everything works fine:
{
firstName: "Sara",
lastName: "Smith",
created: "2018/05/10",
birthDate: "1977/09/12",
isDeleted: false
}
If I send this as my body to http://localhost:8311/api/v1/Member/12 as a PUT request, the record in my data with ID of 12 gets updated to what you see in the JSON.
What I would like to do though is implement a PATCH verb where I can do partial updates. If Sara gets married, I would like to be able to send this JSON:
{
lastName: "Jones"
}
I would like to be able to send just that JSON and update JUST the LastName field and leave all the other fields alone.
I tried this:
[Route("{id}")]
[HttpPatch]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
}
My problem is that this returns all the fields in the model object (all of them are nulls except the LastName field), which makes sense since I am saying I want a Models.Member object. What I would like to know is if there is a way to detect which properties have actually been sent in the JSON request so I can update just those fields?
I hope this helps using Microsoft JsonPatchDocument:
.Net Core 2.1 Patch Action into a Controller:
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody]JsonPatchDocument<Node> value)
{
try
{
//nodes collection is an in memory list of nodes for this example
var result = nodes.FirstOrDefault(n => n.Id == id);
if (result == null)
{
return BadRequest();
}
value.ApplyTo(result, ModelState);//result gets the values from the patch request
return NoContent();
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex);
}
}
Node Model class:
[DataContract(Name ="Node")]
public class Node
{
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "node_id")]
public int Node_id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "full_name")]
public string Full_name { get; set; }
}
A valid Patch JSon to update just the "full_name" and the "node_id" properties will be an array of operations like:
[
{ "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
{ "op": "replace", "path": "node_id", "value": 10}
]
As you can see "op" is the operation you would like to perform, the most common one is "replace" which will just set the existing value of that property for the new one, but there are others:
[
{ "op": "test", "path": "property_name", "value": "value" },
{ "op": "remove", "path": "property_name" },
{ "op": "add", "path": "property_name", "value": [ "value1", "value2" ] },
{ "op": "replace", "path": "property_name", "value": 12 },
{ "op": "move", "from": "property_name", "path": "other_property_name" },
{ "op": "copy", "from": "property_name", "path": "other_property_name" }
]
Here is an extensions method I built based on the Patch ("replace") specification in C# using reflection that you can use to serialize any object to perform a Patch ("replace") operation, you can also pass the desired Encoding and it will return the HttpContent (StringContent) ready to be sent to httpClient.PatchAsync(endPoint, httpContent):
public static StringContent ToPatchJsonContent(this object node, Encoding enc = null)
{
List<PatchObject> patchObjectsCollection = new List<PatchObject>();
foreach (var prop in node.GetType().GetProperties())
{
var patch = new PatchObject{ Op = "replace", Path = prop.Name , Value = prop.GetValue(node) };
patchObjectsCollection.Add(patch);
}
MemoryStream payloadStream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(patchObjectsCollection.GetType());
serializer.WriteObject(payloadStream, patchObjectsCollection);
Encoding encoding = enc ?? Encoding.UTF8;
var content = new StringContent(Encoding.UTF8.GetString(payloadStream.ToArray()), encoding, "application/json");
return content;
}
}
Noticed that tt also uses this class I created to serialize the PatchObject using DataContractJsonSerializer:
[DataContract(Name = "PatchObject")]
class PatchObject
{
[DataMember(Name = "op")]
public string Op { get; set; }
[DataMember(Name = "path")]
public string Path { get; set; }
[DataMember(Name = "value")]
public object Value { get; set; }
}
A C# example of how to use the extension method and invoking the Patch request using HttpClient:
var nodeToPatch = new { Name = "TestPatch", Private = true };//You can use anonymous type
HttpContent content = nodeToPatch.ToPatchJsonContent();//Invoke the extension method to serialize the object
HttpClient httpClient = new HttpClient();
string endPoint = "https://localhost:44320/api/nodes/1";
var response = httpClient.PatchAsync(endPoint, content).Result;
Thanks
PATCH operations aren't usually defined using the same model as the POST or PUT operations exactly for that reason: How do you differentiate between a null, and a don't change. From the IETF:
With PATCH, however, the enclosed entity contains a set of
instructions describing how a resource currently residing on the
origin server should be modified to produce a new version.
You can look here for their PATCH suggestion, but sumarilly is:
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
#Tipx's answer re using PATCH is spot on, but as you've probably already found, actually achieving that in a statically typed language like C# is a non-trivial exercise.
In the case where you're using a PATCH to represent a set of partial updates for a single domain entity (e.g. to update the first name and last name only for a contact with many more properties) you need to do something along the lines of looping each instruction in the 'PATCH' request and then applying that instruction to an instance of your class.
Applying an individual instruction will then comprise of
Finding the property of the instance that matches the name in the
instruction, or handling property names you weren't expecting
For an update: Trying to parse the value submitted in the patch into the instance property and handling the error if e.g. the instance property is a bool but the patch instruction contains a date
Deciding what to do with Add instructions as you can't add new properties to a statically typed C# class. One approach is to say that Add means "set the value of the instance's property only if property's existing value is null"
For Web API 2 on the full .NET Framework the JSONPatch github project looks to make a stab at providing this code, although it doesn't look like there's been a lot of development on that repo recently and the readme does state:
This is still very much an early project, don't use it in production
yet unless you understand the source and don't mind fixing a few bugs
;)
Things are simpler on .NET Core as that has a set of functionality to support this in the Microsoft.AspNetCore.JsonPatch namespace.
The rather useful jsonpatch.com site also lists out a few more options for Patch in .NET:
Asp.Net Core JsonPatch (Microsoft official implementation)
Ramone (a framework for consuming REST services, includes a JSON Patch implementation)
JsonPatch (Adds JSON Patch support to ASP.NET Web API)
Starcounter (In-memory Application Engine, uses JSON Patch with OT for client-server sync)
Nancy.JsonPatch (Adds JSON Patch support to NancyFX)
Manatee.Json (JSON-everything, including JSON Patch)
I need to add this functionality to an existing Web API 2 project of ours, so I'll update this answer if I find anything else that's useful while doing that.
I wanted to achieve exactly the same thing, but used a different method to others described here. I've created a working repo using this if you want to check it out:
https://github.com/emab/patch-example
If you have the following two models:
Database model
public class WeatherDBModel
{
[Key]
public int Id { get; set; }
public string City { get; set; }
public string Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
public Weather(int id, string city, string country, double temperature, double windSpeed, double rain)
{
Id = id;
City = city;
Country = country;
Temperature = temperature;
WindSpeed = windSpeed;
Rain = rain;
}
}
Update model
Containing exact names of database model properties. Includes properties which can be updated
public class WeatherUpdateModel
{
public string? City { get; set; }
public string? Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
}
This update model is sent to the service layer along with the id of the object you'd like to update.
You can then implement the following method in your repository layer which maps any non-null values from the updateModel into an existing entity if it has been found:
public Weather Update(int id, WeatherUpdate updateObject)
{
// find existing entity
var existingEntity = _context.Weather.Find(id);
// handle not found
if (existingEntity == null)
{
throw new EntityNotFoundException(id);
}
// iterate through all of the properties of the update object
// in this example it includes all properties apart from `id`
foreach (PropertyInfo prop in updateObject.GetType().GetProperties())
{
// check if the property has been set in the updateObject
// if it is null we ignore it. If you want to allow null values to be set, you could add a flag to the update object to allow specific nulls
if (prop.GetValue(updateObject) != null)
{
// if it has been set update the existing entity value
existingEntity.GetType().GetProperty(prop.Name)?.SetValue(existingEntity, prop.GetValue(updateObject));
}
}
_context.SaveChanges();
return existingEntity;
}
Using this method you can change your models without worrying about the update logic, as long as you ensure that the UpdateModel is kept up-to-date with the database model.
If a property of your object was omitted in your JSON, ASP.NET won't "set" that property on the object, the property will have its default value. In order to know which properties were sent with the JSON object you need to have a way to detect which properties of the object were set.
In order to detect which properties have "actually been sent" with the JSON object, you can modify your Member class to contain a collection of property names that were "set". Then, for all properties that you want to be able to know if they were sent in the JSON object make that when the property is set the name of the property should be added to the collection of set properties.
public class Member
{
private string _firstName;
private string _lastName;
...
private bool _isDeleted;
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
_setProperties.Add(nameof(FirstName));
}
}
public string LastName
{
get => _lastName;
set
{
_lastName = value;
_setProperties.Add(nameof(LastName));
}
}
...
public bool IsDeleted
{
get => _isDeleted;
set
{
_isDeleted= value;
_setProperties.Add(nameof(IsDeleted));
}
}
private readonly HashSet<string> _setProperties = new HashSet<string>();
public HashSet<string> GetTheSetProperties()
{
return new HashSet<string>(_setProperties);
}
}
In the UpdateRow method you can now check whether a property was sent in the JSON by checking if it is in the _setProperties collection. So if you want to see if the LastName was sent in the JSON just do
bool lastNameWasInJson = model.Contains(nameof(model.LastName));
Following up to Avid Learners approach. I found this easy to add to an existing PUT method.
Alternatively to avoid loading twice you could apply update operations and then before saving apply the patch, but I'd rather load twice and have simple code.
public ResultModel Patch(UpdateModel model)
{
var record = LoadAsUpdateModel(model.Id);
if (record == null) return null;
foreach(var propertyName in model.SetProperties())
{
var property = model.GetType().GetProperty(propertyName);
property.SetValue(record, property.GetValue(model));
}
return Update(record);
}
I have a Json service I cannot alter as it is not mine.
Their Json is a formatted in a way that parsing it is difficult. It looks something like this.
"people": {
"Joe Bob": {
"name": "Joe Bob",
"id": "12345"
},
"Bob Smith": {
"name": "Bob Smith",
"id": "54321"
}
},
I would really prefer this was laid out like a JSon array, however it presently is not.
I am wondering the best approach here. Should I alter the Json to look like an array before I parse it or load up the ExtensionData and parse it from that?
There are other items in the feed that I do not have issue with. Just stuck with this one section.
Thanks
You can use json.net to deserialize the data (the json you pasted, and doing only one parsing, without modifying anything).
using dynamic foo = JsonConvert.DeserializeObject<dynamic>(data)
than, you can iterate the list using foo.people, accessing the Name and Value.
you can create a class (if you know what the schema is, and to deserialize the data into a list of the given class such as:
public class People
{
[JsonProperty(PropertyName="people")]
public IDictionary<string, Person> Persons { get; set; }
}
public class Person
{
[JsonProperty(PropertyName="name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
}
and than call:
var obj = JsonConvert.DeserializeObject<People>(data);
foreach (var item in obj.Persons.Values)
{
//item is instance of Person
}
Another good and possible option will be:
How can I navigate any JSON tree in c#?
Does anyone know how to convert a string which contains json into a C# array. I have this which reads the text/json from a webBrowser and stores it into a string.
string docText = webBrowser1.Document.Body.InnerText;
Just need to somehow change that json string into an array. Been looking at Json.NET but I'm not sure if that's what I need, as I don't want to change an array into json; but the other way around. Thanks for the help!
just take the string and use the JavaScriptSerializer to deserialize it into a native object. For example, having this json:
string json = "[{Name:'John Simith',Age:35},{Name:'Pablo Perez',Age:34}]";
You'd need to create a C# class called, for example, Person defined as so:
public class Person
{
public int Age {get;set;}
public string Name {get;set;}
}
You can now deserialize the JSON string into an array of Person by doing:
JavaScriptSerializer js = new JavaScriptSerializer();
Person [] persons = js.Deserialize<Person[]>(json);
Here's a link to JavaScriptSerializer documentation.
Note: my code above was not tested but that's the idea Tested it. Unless you are doing something "exotic", you should be fine using the JavascriptSerializer.
using Newtonsoft.Json;
Install this class in package console
This class works fine in all .NET Versions, for example in my project: I have DNX 4.5.1 and DNX CORE 5.0 and everything works.
Firstly before JSON deserialization, you need to declare a class to read normally and store some data somewhere
This is my class:
public class ToDoItem
{
public string text { get; set; }
public string complete { get; set; }
public string delete { get; set; }
public string username { get; set; }
public string user_password { get; set; }
public string eventID { get; set; }
}
In HttpContent section where you requesting data by GET request
for example:
HttpContent content = response.Content;
string mycontent = await content.ReadAsStringAsync();
//deserialization in items
ToDoItem[] items = JsonConvert.DeserializeObject<ToDoItem[]>(mycontent);
Yes, Json.Net is what you need. You basically want to deserialize a Json string into an array of objects.
See their examples:
string myJsonString = #"{
"Name": "Apple",
"Expiry": "\/Date(1230375600000+1300)\/",
"Price": 3.99,
"Sizes": [
"Small",
"Medium",
"Large"
]
}";
// Deserializes the string into a Product object
Product myProduct = JsonConvert.DeserializeObject<Product>(myJsonString);
Old question but worth adding an answer if using .NET Core 3.0 or later. JSON serialization/deserialization is built into the framework (System.Text.Json), so you don't have to use third party libraries any more. Here's an example based off the top answer given by #Icarus
using System;
using System.Collections.Generic;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var json = "[{\"Name\":\"John Smith\", \"Age\":35}, {\"Name\":\"Pablo Perez\", \"Age\":34}]";
// use the built in Json deserializer to convert the string to a list of Person objects
var people = System.Text.Json.JsonSerializer.Deserialize<List<Person>>(json);
foreach (var person in people)
{
Console.WriteLine(person.Name + " is " + person.Age + " years old.");
}
}
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
}
}
}
One Situation that wasn't covered in the other responses is when you don't know the type of what the JSON object contains. That was my case as I needed to be able to NOT type it and leave it dynamic.
var objectWithFields = js.Deserialize<dynamic[]>(json);
Note: it is definitely preferred to have a type, in some cases, it is not possible, that's why I added this answer.