Validating JSON Schema in C# - Is there a better method? - c#

Currently I am building some functionality that takes an object (in my case a blog post) in JSON format and validates it meets the 'Post' schema before deserializing it into an object. This is my current solution -
{
const string rawPost = #"{
""userId"": 1,
""id"": 200,
""title"": ""hello"",
""body"": ""body""
}";
JSchemaGenerator generator = new JSchemaGenerator();
JSchema schema = generator.Generate(typeof(Post));
var isPost = IsValid(JObject.Parse(rawPost), schema);
if (isPost)
{
var post = JsonConvert.DeserializeObject<Post>(rawPost);
}
}
public static bool IsValid(JObject jObject, JSchema jSchema)
{
return jObject.IsValid(jSchema);
}
public class Post
{
public int userId { get; set; }
public int id { get; set; }
public string title { get; set; }
public string body { get; set; }
}
}
Currently I do not like the fact that my 'Post' object in this case has incorrectly cased properties and breaks normal conventions - Which means the object is not very reusable. Is there another way of achieving the same thing with Pascal Casing?

You could decorate the properties of your class with the NewtonSoft JsonProperty attribute, setting the PropertyName as follows:
using Newtonsoft.Json;
public class Post
{
[JsonProperty(PropertyName = "userId")]
public int UserId { get; set; }
[JsonProperty(PropertyName = "id ")]
public int Id { get; set; }
[JsonProperty(PropertyName = "title ")]
public string Title { get; set; }
[JsonProperty(PropertyName = "body ")]
public string Body { get; set; }
}
The JsonSerializer object will use these in its deserialization.
Edit: or as per the linked duplicate question

Related

Convert JSON object to Model in .NET

I have this JSON object returned from API:
[
{
"batchId": 789,
"debtId": 1841,
"dateAdded": "2021-07-27T16:01:39.41",
"debtCategoryId": 2,
"agreementNumber": 78262155,
"clientNumber": 1068055,
"clientName": "Client Two"
},
{
"batchId": 866,
"debtId": 1918,
"dateAdded": "2021-08-25T14:47:18.13",
"debtCategoryId": 2,
"agreementNumber": 1000140792,
"clientNumber": 11213287,
"clientName": "Client One"
}
]
I'm trying to convert that to a C# object which has this structure:
public class DebtConfirmationResponse
{
public List<DebtConfirmation> DebtConfirmations { get; set; }
}
Where DebtConfirmation has these properties:
public class DebtConfirmation
{
public int BatchId { get; set; }
public int DebtId { get; set; }
public string DateAdded { get; set; }
public int DebtCategoryId { get; set; }
public string AgreementNumber { get; set; } = string.Empty;
public string ClientNumber { get; set; } = string.Empty;
public string ClientName { get; set; } = string.Empty;
}
The error I'm getting is:
the json value could not be converted to the name of the model path $
linenumber 0 bytepositioninline 1
Is there anything wrong with the way how the model is being setup?
I also tried converting to the same model with batch id only as a property and I got the same message.
You define AgreementNumber, ClientNumber as strings in your C# code, but this properties is numbers in json, so you have to define it as longs.
And the another point is that you don't need a wrapper around DebtConfirmation class. Deserealize your json into ICollection, IList or just List of DebtConfirmation objects.
I used the quicktype.io for retrieving C# classes from json example you provide. This is very helpful for those who doesn't want to manually generate the models for their JSON strings.
Here is the code sample.
The output is:
789
866
using System.Text.Json;
using System.Text.Json.Serialization;
string json = "[\n {\n \"batchId\": 789,\n \"debtId\": 1841,\n \"dateAdded\": \"2021-07-27T16:01:39.41\",\n \"debtCategoryId\": 2,\n \"agreementNumber\": 78262155,\n \"clientNumber\": 1068055,\n \"clientName\": \"Client Two\"\n },\n {\n \"batchId\": 866,\n \"debtId\": 1918,\n \"dateAdded\": \"2021-08-25T14:47:18.13\",\n \"debtCategoryId\": 2,\n \"agreementNumber\": 1000140792,\n \"clientNumber\": 11213287,\n \"clientName\": \"Client One\"\n }\n]";
var data = JsonSerializer.Deserialize<ICollection<DebtConfirmation>>(json);
foreach (DebtConfirmation current in data)
{
Console.WriteLine(current.BatchId);
}
public partial class DebtConfirmation
{
[JsonPropertyName("batchId")]
public long BatchId { get; set; }
[JsonPropertyName("debtId")]
public long DebtId { get; set; }
[JsonPropertyName("dateAdded")]
public DateTimeOffset DateAdded { get; set; }
[JsonPropertyName("debtCategoryId")]
public long DebtCategoryId { get; set; }
[JsonPropertyName("agreementNumber")]
public long AgreementNumber { get; set; }
[JsonPropertyName("clientNumber")]
public long ClientNumber { get; set; }
[JsonPropertyName("clientName")]
public string ClientName { get; set; }
}
You can use #GeorgyTarasov's answer. But it is definitely not the simplest option.
You can simply use JsonNamingPolicy.CamelCase.
Then you would simply do...
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};
var ret = JsonSerializer.Deserialize<DebtConfirmation>(payload, options);
If you are using AspNet Core, you can register the option here
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
});

Deserialize JSON having keys with CamelCase and snake_case naming in C#

I am receiving a json file via api, that json file will be converted into a class that I cant touch and has around 400 properties. the json is using for the key names CamelCase and in the same json some keys are in the format of snake_case.
I am currently using System.Text.Json but open to change to Newtonsoft.json is needed.
I tried to create a JsonSnakeCaseNamingPolicy class (only converting the property names to snake_case) and used in the JsonSerializerOptions like this:
var deserializeOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
};
var flexImport = JsonSerializer.Deserialize<List<FlexImport>>(input.MappedObjectJson, deserializeOptions);
But then the properties in CamelCase don't get populated. Any idea on how to achieve this situation?
This the json sample:
[{\"BatchId\":123,\"Title_Id\":123,\"CurrentNumber\":\"aa705128\",\"address\":\"122 BLACKSGATE EN\",\"curr_interest_rate\":4},{\"BatchId\":2,\"Title_Id\":1,\"CurrentNumber\":\"27705128\",\"address\":\"90 ARMA DR\",\"curr_interest_rate\":5},{\"BatchId\":2,\"Title_Id\":2,\"CurrentNumber\":\"30877674\",\"address\":\"6485 N SIN CIR\",\"curr_interest_rate\":4}]"
And here is part of the destination class:
public class FlexImport
{
public long BatchId { get; set; }
public long TitleId { get; set; }
public string CurrentNumber { get; set; }
public string Address { get; set; }
public decimal? CurrInterestRate { get; set; }
}
Use JSON attributes
public class FlexImport
{
public long BatchId { get; set; }
[JsonPropertyName("Title_Id")]
public long TitleId { get; set; }
public string CurrentNumber { get; set; }
[JsonPropertyName("address")]
public string Address { get; set; }
[JsonPropertyName("curr_interest_rate")]
public decimal? CurrInterestRate { get; set; }
}
Etc. Adjust as needed.

C# Parsing JSON using JObject.Parse to ignore missing fields

Is it possible to have JObject.Parse ignore missing fields?
From my example below you can see that I have declared a class Address and using JsonProperty to specifying alternate field names.
I have provided 3 examples, there are 3 JSON strings which have a slightly different structure, only Example 1 matches and returns an object, Examples 2 and 3 return a null because there is a missing field.
Is there a way to use other JsonProperty's to allow them to be ignored if not provided?
public class Address
{
[JsonProperty("flat_number")]
public string FlatNumber { get; set; }
[JsonProperty("house_number")]
public string HouseNumber { get; set; }
[JsonProperty("address")]
public string Address1 { get; set; }
[JsonProperty("address2")]
public string Address2 { get; set; }
[JsonProperty("town")]
public string Town { get; set; }
[JsonProperty("postcode")]
public string Postcode { get; set; }
}
private static T TryParse<T>(string json) where T : new()
{
var jSchemaGenerator = new JSchemaGenerator();
const string license = "license";
License.RegisterLicense(license);
var jSchema = jSchemaGenerator.Generate(typeof(T));
var jObject = JObject.Parse(json);
return jObject.IsValid(jSchema) ? JsonConvert.DeserializeObject<T>(json) : default(T);
}
//Example 1 with house_number and flat_number
const string json = "{\"house_number\":\"40\",\"flat_number\":\"82\",\"address\":\"Somewhere\",\"address2\":\"Over\",\"town\":\"There\",\"postcode\":\"ZZ991AA\"}";
//Example 2 with house_number but not flat_number
//const string json = "{\"house_number\":\"40\",\"address\":\"Somewhere\",\"address2\":\"Over\",\"town\":\"There\",\"postcode\":\"ZZ991AA\"}";
//Example 3 with flat_number but not house_number
//const string json = "{\"flat_number\":\"82\",\"address\":\"Somewhere\",\"address2\":\"Over\",\"town\":\"There\",\"postcode\":\"ZZ991AA\"}";
var tryParse = TryParse<AddressTest>(json);
if (tryParse != null)
{
}
You can use JsonSerializerSettings to perform this operation. This will ignore your
missing members.
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
JsonConvert.DeserializeObject<YourClass>(jsonResponse, jsonSerializerSettings);
If you json contains some key/value pair as dynamic means you don't know if these key/value pairs is exist in json or not
If your objects are not fixed and data must be configurable then Newtonsoft.json has one feature that to be use here and that is [JsonExtensionData]. Read more
Extension data is now written when an object is serialized. Reading and writing extension data makes it possible to automatically round-trip all JSON without adding every property to the .NET type you’re deserializing to. Only declare the properties you’re interested in and let extension data do the rest.
Now your house_number and flat_number will be collected in [JsonExtensionData] so you no need to handle missing fields anymore.
So your Address class will be
public class Address
{
[JsonExtensionData]
public IDictionary<string, JsonToken> extensionData { get; set; }
[JsonProperty("address")]
public string Address1 { get; set; }
[JsonProperty("address2")]
public string Address2 { get; set; }
[JsonProperty("town")]
public string Town { get; set; }
[JsonProperty("postcode")]
public string Postcode { get; set; }
}
After a little more digging I found a JsonProperty that allows nulls to be ignored, by applying NullValueHandling = NullValueHandling.Ignore to both FlatNumber and HouseNumber all examples return an object. Therefore modifying the Address class as per my example below works in conjunction with my original code.
public class Address
{
[JsonProperty("flat_number", NullValueHandling = NullValueHandling.Ignore)]
public string FlatNumber { get; set; }
[JsonProperty("house_number", NullValueHandling = NullValueHandling.Ignore)]
public string HouseNumber { get; set; }
[JsonProperty("address")]
public string Address1 { get; set; }
[JsonProperty("address2")]
public string Address2 { get; set; }
[JsonProperty("town")]
public string Town { get; set; }
[JsonProperty("postcode")]
public string Postcode { get; set; }
}

Deserialize nested JSON Response with RestSharp Client

I'd like to consume a REST Api and deserialize the nested JSON Response. For that purpose I tried to create some POCO classes which represent the JSON Response [1].
The response looks like this:
{
"success": true,
"message": "OK",
"types":
[
{
"name": "A5EF3-ASR",
"title": "ITIL Foundation Plus Cloud Introduction",
"classroomDeliveryMethod": "Self-paced Virtual Class",
"descriptions": {
"EN": {
"description": "some Text null",
"overview": null,
"abstract": "Some other text",
"prerequisits": null,
"objective": null,
"topic": null
}
},
"lastModified": "2014-10-08T08:37:43Z",
"created": "2014-04-28T11:23:12Z"
},
{
"name": "A4DT3-ASR",
"title": "ITIL Foundation eLearning Course + Exam",
"classroomDeliveryMethod": "Self-paced Virtual Class",
"descriptions": {
"EN": {
"description": "some Text"
(...)
So I created the following POCO classes:
public class Course
{
public bool success { get; set; }
public string Message { get; set; }
public List<CourseTypeContainer> Type { get; set; }
}
/* each Course has n CourseTypes */
public class CourseType
{
public string Name { get; set; }
public string Title { get; set; }
public List<CourseTypeDescriptionContainer> Descriptions { get; set; }
public DateTime LastModified { get; set; }
public DateTime Created { get; set; }
}
public class CourseTypeContainer
{
public CourseType CourseType { get; set; }
}
/* each CourseType has n CourseTypeDescriptions */
public class CourseTypeDescription
{
public string Description { get; set; }
public string Overview { get; set; }
public string Abstract { get; set; }
public string Prerequisits { get; set; }
public string Objective { get; set; }
public string Topic { get; set; }
}
public class CourseTypeDescriptionContainer
{
public CourseTypeDescription CourseTypeDescription { get; set; }
}
And this is the API Code:
var client = new RestClient("https://www.someurl.com");
client.Authenticator = new HttpBasicAuthenticator("user", "password");
var request = new RestRequest();
request.Resource = "api/v1.0/types";
request.Method = Method.GET;
request.RequestFormat = DataFormat.Json;
var response = client.Execute<Course>(request);
EDIT 1: I found a Typo, the Type property in AvnetCourse should be named Types:
public List<AvnetCourseTypeContainer> Type { get; set; } // wrong
public List<AvnetCourseTypeContainer> Types { get; set; } // correct
Now the return values look like:
response.Data.success = true // CORRECT
repsonse.Data.Message = "OK" // CORRECT
response.Data.Types = (Count: 1234); // CORRECT
response.Data.Types[0].AvnetCourseType = null; // NOT CORRECT
EDIT 2: I implemented the Course.Types Property using a List<CourseType> instead of a List<CourseTypeContainer>, as proposed by Jaanus. The same goes for the CourseTypeDescriptionContainer:
public List<CourseTypeContainer> Type { get; set; } // OLD
public List<CourseTypeDescriptionContainer> Descriptions { get; set; } // OLD
public List<CourseType> Type { get; set; } // NEW
public List<CourseTypeDescription> Descriptions { get; set; } // NEW
Now the response.Data.Types finally are properly filled. However, the response.Data.Types.Descriptions are still not properly filled, since there is an additional language layer (e.g. "EN"). How can I solve this, without creating a PACO for each language?
EDIT 3: I had to add an additional CourseTypeDescriptionDetails class, where I would store the descriptive Data. In my CourseTypeDescription I added a property of the Type List for each language. Code Snippet:
public class AvnetCourseType
{
public List<CourseTypeDescription> Descriptions { get; set; }
// other properties
}
public class CourseTypeDescription
{
public List<CourseTypeDescriptionDetails> EN { get; set; } // English
public List<CourseTypeDescriptionDetails> NL { get; set; } // Dutch
}
public class CourseTypeDescriptionDetails
{
public string Description { get; set; }
public string Overview { get; set; }
public string Abstract { get; set; }
public string Prerequisits { get; set; }
public string Objective { get; set; }
public string Topic { get; set; }
}
It works now, but I need to add another property to CourseTypeDescription for each language.
OLD: The return values are
response.Data.success = true // CORRECT
repsonse.Data.Message = "OK" // CORRECT
response.Data.Type = null; // WHY?
So why does my response.Type equal null? What am I doing wrong?
Thank you
Resources:
[1] RestSharp Deserialization with JSON Array
Try using this as POCO:
public class Course
{
public bool success { get; set; }
public string message { get; set; }
public List<CourseTypeContainer> Types { get; set; }
}
Now you have list of CourseTypeContainer.
And CourseTypeContainer is
public class CourseTypeContainer
{
public CourseType CourseType { get; set; }
}
So when you are trying to get response.Data.Types[0].AvnetCourseType , then you need to have field AvnetCourseType inside CourseTypeContainer
Or I think what you want is actually this public List<CourseType> Types { get; set; }, you don't need a container there.
Just in case this helps someone else, I tried everything here and it still didn't work on the current version of RestSharp (106.6.2). RestSharp was completely ignoring the RootElement property as far as I could tell, even though it was at the top level. My workaround was to manually tell it to pull the nested JSON and then convert that. I used JSON.Net to accomplish this.
var response = restClient.Execute<T>(restRequest);
response.Content = JObject.Parse(response.Content)[restRequest.RootElement].ToString();
return new JsonDeserializer().Deserialize<T>(response);
I used http://json2csharp.com/ to create C# classes from JSON.
Then, renamed RootObject to the ClassName of the model file I'm creating
All the data in the nested json was accessible after RestSharp Deserializitaion similar to responseBody.data.Subject.Alias
where data, Subject and Alias are nested nodes inside the response JSON received.

Deserialization JSON string with JSON.NET

I am helpless, I need deserialize JSON string in this format to class.
JSON string:
{
"newNickInfo": {
"2775040": {
"idUser": 2775040,
"nick": "minci88",
"sefNick": "minci88",
"sex": 2,
"photon": "http:\/\/213.215.107.125\/fotky\/277\/50\/n_2775040.jpg?v=4",
"photos": "http:\/\/213.215.107.125\/fotky\/277\/50\/s_2775040.jpg?v=4",
"logged": false,
"idChat": 0,
"roomName": "",
"updated": 1289670130
}
}
}
Class:
public class User
{
public string idUser { get; set; }
public string nick { get; set; }
public string sefNick { get; set; }
public string sex { get; set; }
public string photon { get; set; }
public string photos { get; set; }
public bool logged { get; set; }
public int idChat { get; set; }
public string roomName { get; set; }
public string updated { get; set; }
}
First problem is, class properties must be lower case, and second problem is I try many ways but I don’t know how cut json object from this json string and deserialize in class.
Any ides? Thank for any help.
One more question, what format would have c# class, if I want deserialize this json string into it.
looks like your JSON string is not correct. Look at this
string json = #"[
{
"newNickInfo": "2775040",
"idUser": "2775040",
"nick":"minci88",
"sefNick":"minci88",
"sex":2,
"photon":"http:\/\/213.215.107.125\/fotky\/277\/50\/n_2775040.jpg?v=4",
"photos":"http:\/\/213.215.107.125\/fotky\/277\/50\/s_2775040.jpg?v=4",
"logged":false,
"idChat":0,
"roomName":"",
"updated":"1289670130"
}
]";
public class User
{
[JsonProperty]
public string idUser{get;set;}
[JsonProperty]
public string nick{get;set;}
[JsonProperty]
public string sefNick{get;set;}
[JsonProperty]
public string sex{get;set;}
[JsonProperty]
public string photon{get;set;}
[JsonProperty]
public string photos{get;set;}
[JsonProperty]
public bool logged{get;set;}
[JsonProperty]
public int idChat{get;set;}
[JsonProperty]
public string roomName{get;set;}
[JsonProperty]
public string updated{get;set;}
}
List<User> users = JsonConvert.DeserializeObject<List<User>>(json);
User u1 = users[0];
Console.WriteLine(u1.nick);
using System.Web.Script.Serialization;
var serializer = new JavaScriptSerializer();
var deserialized = serializer.Deserialize<Dictionary<string, Dictionary<string, User>>>(theJsonString);
var theUser = deserialized["newNickInfo"]["2775040"];
I use this tool Jsonclassgenerator. It could generate C# classes for the input Json string. You dont have to use the exact class generated. But you can see the format of the class and create a similar one in your project.

Categories