C# deserialize selected properties in json - c#

Given the following JSON
{
"enabled": true,
"name": "Name",
"description": "Test",
"rules": [
{
"propA": "a",
"propB": "b"
}
]
}
Is it possible in C# to deserialize on selected properties based on an input list:
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
The example json is a simplified example, the real one can contain hundred of properties. The use case is to only return the fields that matches the input list in a dynamic or anonymous object and discard the other properties.

using Newtonsoft.Json.Linq;
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
var splitted = propertiesToInclude.SelectMany(x => x.Split('.'));
string text = File.ReadAllText("test.json");
var json = JToken.Parse(text);
Process(json);
Console.WriteLine(json);
void Process(JToken token)
{
if (token is JObject jObject)
{
jObject.Properties()
.Where(x => !splitted.Contains(x.Name, StringComparer.OrdinalIgnoreCase))
.ToList()
.ForEach(x => x.Remove());
foreach (var x in jObject)
Process(x.Value);
}
else if (token is JArray jArray)
{
foreach (var x in jArray)
Process(x);
}
}
This code on the data shown will give the desired result.
The output is a JToken object containing the desired properties.
However, I used a simple search for all occurrences of names in a splited array. This will give false positives if, for example, the root object contains the propA property or the object in the array contains the description property.
To avoid this, you need to compare the JToken.Path property with the propertiesToInclude elements, taking into account the depth.

Related

How to remove a key value pair within a nested json structure in C#

I have the following json
{
"audit_date": "2020-05-13T11:27:10.3187798Z",
"client_uuid": "2fd77dd8-ed76-4bba-b0e1-5cda454c8d6e",
"audit_entry": {
"where_uri": "test.com/dataservice/apps/171f0841-825b-4964-8f8c-0869650f14a6",
"why_uri": "test.com/dataservice/reference/reasons_for_change/61acc173-7168-4ae5-9f04- afa228941f8b",
"who_uri": "test.com/securityservice/users/4977dae1-a307-425f-980c-53413fef1b0f",
"when_audited": "2018-11-13T20:20:39+00:00",
"what_uri": "test.com/dataservice/study_subjects/1bc67a71-8549-4ab8-9dd9-e44238198860",
"what_changed": [
{
"attribute_name": "birth_year",
"attribute_value": "1969",
"attribute_change": "1970"
},
{
"attribute_name": "subject_reference",
"attribute_value": "TEST-WOO3444",
"attribute_change": null
}
]
}
}
And I want to remove the second attribute_change key value pair to be as follows
{
"audit_date": "2020-05-13T11:27:10.3187798Z",
"client_uuid": "2fd77dd8-ed76-4bba-b0e1-5cda454c8d6e",
"audit_entry": {
"where_uri": "test.com/dataservice/apps/171f0841-825b-4964-8f8c-0869650f14a6",
"why_uri": "test.com/dataservice/reference/reasons_for_change/61acc173-7168-4ae5-9f04- afa228941f8b",
"who_uri": "test.com/securityservice/users/4977dae1-a307-425f-980c-53413fef1b0f",
"when_audited": "2018-11-13T20:20:39+00:00",
"what_uri": "test.com/dataservice/study_subjects/1bc67a71-8549-4ab8-9dd9-e44238198860",
"what_changed": [
{
"attribute_name": "birth_year",
"attribute_value": "1969",
"attribute_change": "1970"
},
{
"attribute_name": "subject_reference",
"attribute_value": "TEST-WOO3444",
}
]
}
}
I have tried the following code
JObject jObject = JObject.Parse(jsonText);
JObject jObj = (JObject)jObject.SelectToken("audit_entry");
//remove second attribute changed token
jObj.Property("what_changed")("attribute_change")[1].Remove();
string json = jObj.ToString(Formatting.None);
I know the syntax is wrong for jObj.Property but after a few hours of googling I cannot find the answer.
Any help much appreciated.
You can remove a property from JObject by calling Remove method with corresponding property name. For example:
JObject jObject = JObject.Parse(jsonText);
// find required object, there are other options
// for example (JObject)jObject["audit_entry"]["what_changed"][1]
var nested = (JObject)jObject.SelectToken("$.audit_entry.what_changed[1]");
//remove attribute changed token
nested.Remove("attribute_change");
string json = jObject.ToString(Formatting.None); // attribute_change is removed from jObject
If you want to remove all properties with such name and null for value you can do next:
var toRemove = jObject.Descendants()
.OfType<JProperty>()
.Where(prop => prop.Name == "attribute_change" && prop.Value.Type == JTokenType.Null)
.ToList();
foreach (var prop in toRemove)
{
prop.Remove();
}
Since what_changed is an array, you should cast to JArray to be able to access its elements and remove attribute_change token
var jObject = JObject.Parse(jsonText);
if (jObject["audit_entry"]?["what_changed"] is JArray array)
if (array[1] is JObject attribute)
attribute.Remove("attribute_change");
Console.WriteLine(jObject);
Search for the beginning attribute name and its end. Then concatenate the json string before/after into a new string. Perhaps a hack, but it will keep you coding until you find a more elegant solution.

How to get property value from .net dynamic array object

I've got a json file containing
{
"Accounts": null,
"AccountTypes": null,
"Actions": null,
"Photos": [
{
"Instance": "...",
"Key": "..."
},
....
]
}
Now I want to get all the Instance properties from the Photo objects. I've got the following code:
var photos = new List<Photo>();
string json = File.ReadAllText(file);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json, typeof(object));
var jsonPhotos = jsonObj.Photos as IEnumerable<dynamic>;
var instances = jsonPhotos.Select(x => x.Instance);
foreach (var instance in instances)
photos.Add(new Photo
{
Document = Convert.FromBase64String(instance)
});
However, jsonPhotos.Select(x => x.Instance); isn't returning anything...
I am able to get things working by using
var instances = new List<string>();
foreach (var photo in jsonPhotos)
instances.Add(photo.Instance.Value);
But can I solve this in a LINQ way?
Why just don't use Json.Linq for that? Parse JSON to JObject instance, then map every token from Photos array to Photo instance (I've omitted Convert.FromBase64String because OP sample doesn't have a valid base64 data, but converting Instance value can be easily added)
var json = JObject.Parse(jsonString);
var photos = json["Photos"]
.Select(token => new Photo
{
Document = token["Instance"]?.Value<string>()
})
.ToList();
The .Select(x => x.Instance) indeed returns ... on .NET Core 3.1. Can you verify that the contents of the json variable are actually what you expect?
Specifically
jsonPhotos.Select(x => x.Instance);
works as expected, while
jsonPhotos.Select(x => x.SomeNonExistingProperty);
enumerates nothing / empty values.
For example, this code prints Instance A, then Instance B, then nothing twice:
var json = #"
{
""Photos"": [
{
""Instance"": ""Instance A"",
""Key"": ""...""
},
{
""Instance"": ""Instance B"",
""Key"": ""...""
}]
}";
var jsonObj = JsonConvert.DeserializeObject<dynamic>(json);
var jsonPhotos = jsonObj.Photos as IEnumerable<dynamic>;
var instances = jsonPhotos.Select(x => x.Instance);
foreach (var instance in instances)
{
Console.WriteLine(instance);
}
// In contrast, this one will print empty lines.
instances = jsonPhotos.Select(x => x.SomeNonExistingProperty);
foreach (string instance in instances)
{
Console.WriteLine(instance);
}
I took the liberty to change the deserialization to dynamic directly, but it also works with the original code from the question.

Sorting JObject inside array by a field value

I have a JSON string like below:
{
"MetaData": {
"ResourcesUsed": 1
},
"Result": [
{
"locations": [
{
"country": "Papua New Guinea",
"city": "Jacquinot Bay",
"locTypeAttributes": {
"localDate": "2018-10-08T04:21:00-07:00",
"utcDate": "2018-10-08T04:21:00-07:00",
},
"point": {
"coordinates": [
151.52,
-5.6
],
"type": "Point"
}
},{
"country": "Papua New Guinea2",
"city": "Jacquinot Bay2",
"locTypeAttributes": {
"localDate": "2018-10-08T04:21:00-07:00",
"utcDate": "2018-10-02T04:21:00-07:00",
},
"point": {
"coordinates": [
151.52,
-5.6
],
"type": "Point"
}
}
]
}
]
}
I converted it to a JSON object using Newtonsoft. What I want to do is to sort the locations array(s) inside the Result array by the utcDate field nested in each locations item. I found the following thread: C# Sort JSON string keys. However, I could not still implement it since I have arrays inside my object, while that question deals purely with sorting objects inside objects alphabetically by property name.
Here is a piece of code that I wrote so far:
public string GenerateJson()
{
var model = (JObject)JsonConvert.DeserializeObject(data);
Sort(model);
}
private void Sort(JObject jObj)
{
var props = jObj["Result"][0]["locations"].ToList();
foreach (var prop in props)
{
prop.Remove();
}
foreach (var prop in props.OrderBy(p => p.Name))
{
jObj.Add(prop);
if (prop.Value is JObject)
Sort((JObject)prop.Value);
if (prop.Value is JArray)
{
Int32 iCount = prop.Value.Count();
for (Int32 iIterator = 0; iIterator < iCount; iIterator++)
if (prop.Value[iIterator] is JObject)
Sort((JObject)prop.Value[iIterator]);
}
}
}
You can sort each individual "Result[*].locations" array using LINQ as follows:
// Load the JSON without parsing or converting any dates.
var model = JsonConvert.DeserializeObject<JObject>(data, new JsonSerializerSettings{ DateParseHandling = DateParseHandling.None });
// Construct a serializer that converts all DateTime values to UTC
var serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings{ DateTimeZoneHandling = DateTimeZoneHandling.Utc });
foreach (var locations in model.SelectTokens("Result[*].locations").OfType<JArray>())
{
// Then sort the locations by utcDate converting the value to UTC at this time.
var query = from location in locations
let utcDate = location.SelectToken("locTypeAttributes.utcDate").ToObject<DateTime>(serializer)
orderby utcDate
select location;
locations.ReplaceAll(query.ToList());
}
Notes:
The JSON is initially loaded using DateParseHandling.None to prevent the "localDate" and "utcDate" strings from being prematurely interpreted as DateTime objects with a uniform DateTime.Kind.
(For a discussion of how Json.NET interprets strings that look like dates, see Serializing Dates in JSON.)
We then iterate through all "locations" arrays using SelectTokens("Result[*].locations") where [*] is the JSONPath wildcard character, selecting all entries in the "Results" array.
We then order each "locations" array by deserializing the nested locTypeAttributes.utcDate to a UTC date, then ordering using LINQ.
Finally the array is updated using JArray.ReplaceAll().
If any locTypeAttributes.utcDate property is missing, an exception will be thrown. You could instead deserialize to DateTime? if that is a possibility.
Working sample .Net fiddle here.

Json.NET (Newtonsoft) to parse somewhat dynamic data (c#)

using c# - I have a string of valid json, and am attempting to parse it into a Dictionary but am struggling with the syntax to do so.
Here's an example of the data I'd like to parse:
{
"data": {
"KeyOne": {
"val": "first!"
"fooBar": "invalid data not needed",
},
"anotherKey": {
"val": null
},
"TheThirdKey": {
"val": 999
"fooFooBarBar": "more unneeded data",
},
"KeyKeyKey": {
"val": "super neato something"
},
...
this needs to be moved into a Dictionary<string, object> with some fairly specific rules:
the ever changing element name is the key ('KeyOne', 'anotherKey'...) - this is unique within the dataset
for the dictionary value, I ONLY need the string or number or null that is the value of 'val' ('first', null, 999, ...)
so my final dictionary should be something like:
"KeyOne" : "first!"
"anotherKey" : null
"TheThirdKey": 999
"KeyKeyKey" : "super neato something"
I've tried to parse this using different variations of
JsonConvert.DeserializeObject<Dictionary<string, object>
I've also tried iterating over the jTokens as such:
JObject jObject = JObject.Parse(jsonString);
List<JToken> jTokens = jObject["data"].Children().ToList();
foreach (JToken jToken in jTokens) { ...
but after so many hours of trying, I am getting embarrassingly nowhere... Hopefully this is something that can be performed with Json.NET, but I have yet to figure it out.
Thoughts?
You could do it this way:
JObject jObject = JObject.Parse(jsonString);
var dataChildren = jObject["data"].Children().Cast<JProperty>();
Dictionary<string, object> result = dataChildren
.ToDictionary(x => x.Name, x => x.Value["val"].Value<JValue>().Value);
You will get a Dictionary<string,object> as a result

Querying JSON from C#

I'm trying to retreive a json item from a json string,this is my json for example:
{
"users":{
"john":{
"password":"0506777031",
"level":1
},
"doe":{
"password":"john",
"level":1
},
"dasda":{
"password":"das",
"level":"1"
},
"zuri":{
"password":"zuri123",
"level":2
}
}
}
I use the json.net library,this is what i've tried so far:
JObject json = JObject.Parse(jsonstring); //this is thr string
JObject match = json["users"].Values<JObject>().Where(m => m["username"].Value<string>() == "itapi" && (m["password"].Value<string>() == "0506777031")).FirstOrDefault();
I'm getting an error on the second line.
This is the error:
Cannot cast Newtonsoft.Json.Linq.JProperty to Newtonsoft.Json.Linq.JToken.
I'm not sure what i'm doing wrong,i will appreciate any help! thanks!
Assuming your question is "What am I doing wrong?", the answer would be
You are trying to typecast what is a JProperty into a JObject (JProperty has a property named Value you can access).
You are not traversing the JSON syntax tree properly.
There is no mention of the "username" within the JSON sample provided.
If the usernames in your example are the property keys (names) "john", "doe", "dasda" and "zuri"... The query you probably want is as follows:
var match = json["users"].Values<JProperty>().Where(m => m.Name == "doe" && m.Value["password"].ToString() == "john").FirstOrDefault();
EDIT: Alternatively, if the username is that key, you can use the direct lookup and assign to the variable match only if the password matches the one you are trying to compare. Also the following version will return the JObject and not the JProperty as it seems you originally wanted. This should also be more efficient.
JObject match;
var temp = json["users"]["doe"];
if(temp["password"].ToString() == "john")
{
match = temp.ToObject<JObject>();
}
Shouldn't it be using square brackets for "users"?
{
"users":[
"john":{
"password":"0506777031",
"level":1
},
"doe":{
"password":"john",
"level":1
},
"dasda":{
"password":"das",
"level":"1"
},
"zuri":{
"password":"zuri123",
"level":2
}
]
}

Categories

Resources