C# Lambda Expression for iterating through nested objects - c#

I have below JSON which I have de-serialized into a C# object and would like to extract the values of INFO_CARD_ID, DOCUMENT_NUM and REVISION_NM at each iteration.
{
"result": {
"message": "Successfully performed search",
"columnlist": "INFO_CARD_ID,DOCUMENT_NUM,REVISION_NM,
"recordcount": 2,
"rows": [
{
"columns": [
{
"value": "UJ3P25HO3JBPLJZ2HU",
"key": "INFO_CARD_ID"
},
{
"value": "DOC1",
"key": "DOCUMENT_NUM"
},
{
"value": "05",
"key": "REVISION_NM"
}
]
},
{
"columns": [
{
"value": "JWQ5TCIV4JC2BCQ4C5",
"key": "INFO_CARD_ID"
},
{
"value": "DOC2",
"key": "DOCUMENT_NUM"
},
{
"value": "05",
"key": "REVISION_NM"
}
]
}
]
}
}
Here are the C# classes:
public class Columns
{
public string value { get; set; }
public string key { get; set; }
}
public class Rows
{
public IList<Columns> columns { get; set; }
}
public class Result
{
public string message { get; set; }
public string columnlist { get; set; }
public int recordcount { get; set; }
public IList<Rows> rows { get; set; }
}
public class searchOutputModel
{
public Result result { get; set; }
}
So far, I have been trying to extract the information on a list with below expression but with no luck, as the individual value is getting stored on each iteration and not all the values at each iteration at the same time.
searchResponse = JsonConvert.DeserializeObject<searchOutputModel>(json);
var dynamicList = searchResponse.result.rows.SelectMany(x => x.columns).Where(y => y.key.Contains("INFO_CARD_ID") || y.key.Contains("DOCUMENT_NUM") || y.key.Contains("REVISION_NM")).Select(y => {
dynamic myValue;
if (y.key.Contains("INFO_CARD_ID"))
myValue = new { INFO_CARD_ID = y.value };
else if (y.key.Contains("DOCUMENT_NUM"))
myValue = new { DOCUMENT_NUM = y.value };
else
myValue = new { REVISION_NM = y.value };
return myValue;
}).ToList();
Expected output:
INFO_CARD_ID DOCUMENT_NUM REVISION_NM
---------------------------------------------
UJ3P25HO3JBPLJZ2HU DOC1 05
JWQ5TCIV4JC2BCQ4C5 DOC2 05
I know I am doing wrong with iteration as it creates new list at each select, but I am not sure how can I achieve the solution that I am looking for. Any help is appreciated.

We just need to select one object per row, no need for SelectMany here. Each property can be gotten by using columns.FirstOrDefault.
I would advise you to create a proper class for this rather than using dynamic but it makes little difference to the logic below.
Not sure why you used Contains on the column.key check, I've used ==
var dynamicList = searchResponse.result.rows.Select(r =>
new {
INFO_CARD_ID = r.columns.FirstOrDefault(c => c.key == "INFO_CARD_ID")?.value,
DOCUMENT_NUM = r.columns.FirstOrDefault(c => c.key == "DOCUMENT_NUM")?.value,
REVISION_NM = r.columns.FirstOrDefault(c => c.key == "REVISION_NM")?.value,
}).ToList();

If you convert each Rows object to a Dictionary, you can use the Dictionary to create objects:
var ans = searchResponse.result.rows
.Select(r => r.columns.ToDictionary(c => c.key, c => c.value))
.Select(cd => new {
INFO_CARD_ID = cd["INFO_CARD_ID"],
DOCUMENT_NUM = cd["DOCUMENT_NUM"],
REVISION_NM = cd["REVISION_NM"]
});

Related

How to return an array of grouped items?

I have a simple GET request working to return all of my elements. But I can't seem to figure out how to return those elements in a grouped array, grouped by name and date.
My model:
public class Event
{
[Key]
public long ID { get; set; }
[Required]
public DateTime date { get; set; }
[Required]
public string name { get; set; }
}
This is my GET request to return all elements:
[Route("api/events")]
[HttpGet]
public async Task<ActionResult<IEnumerable<Event>>> GetEvents()
{
return await _context.Events.ToListAsync();
}
You could define a DTO like this:
public class GroupedEventsDto
{
public DateTime Date { get; set; }
public string Name { get; set; }
// although you already know date and name,
// so you could have the list of IDs only
public List<Event> Events { get; set; }
}
and do it simply like:
[HttpGet("api/events")]
public async Task<ActionResult<IEnumerable<GroupedEventsDto>>> GetEvents()
{
var events = await context.Events.ToArrayAsync();
var data = events
.GroupBy(x => new { x.date, x.name })
.Select(grp => new GroupedEventsDto
{
Date = grp.Key.date,
Name = grp.Key.name,
Events = grp.ToList()
})
.ToList();
return data;
}
This would generate a JSON like
[
{
"Name": "a",
"Date": "2020-08-22",
"Events": [
{
"Name": "a",
"Date": "2020-08-22",
"Id": 1
}, ...
]
},
{
"Name": "b",
"Date": "2020-08-22",
"Events": [
{
"Name": "b",
"Date": "2020-08-22",
"Id": 2
}, ...
]
},
...
]
Of course, which properties you end up needing would change in GroupedEventsDto

C# return a single list into nested JSON API

I have a simple list which is returned from my DB to my MVC API, the sample query is
select school, class, pupil from area
With that I am adding to a list
foreach (DataRow row in table.Rows)
{
var area = new Area();
area.school = row.ItemArray[0].ToString();
area.class = row.ItemArray[1].ToString();
area.pupil = row.ItemArray[2].ToString();
returnedlist.Add(area);
}
at the moment the API just returns the list
{
"School": "",
"Class": "",
"Pupil": ""
},
{
"School": "",
"Class": "",
"Pupil": ""
},
However, I would ideally like it returned nested like the following
[
{
School: "Name",
Class: [
ClassName: '',
Pupil: [
PupilName: ''},
PupilName: ''},
PupilName: '']
},
{
School: "Name",
Class: [
ClassName: '',
Pupil: [
PupilName: ''},
PupilName: ''},
PupilName: '']
},
I may have butchered that but you get the general idea.
The class for the data again is very simple
public class Area
{
public string School { get; set; }
public string Class { get; set; }
public string Pupil { get; set; }
}
So far I have tried returning a list in a list etc, but without any luck.
Any help greatly appreciated.
To get the expected result, you could create class with this structure :
1 - Classes
public class NewArea
{
public string School { get; set; }
public List<Class> Classes { get; set; }
}
public class Class
{
public string Name { get; set; }
public List<string> Pupils { get; set; }
}
2 - First, group by school and for each grouped item, group by class:
List<NewArea> newAreas = returnedlist
.GroupBy(x => x.School)
.Select(x => new NewArea
{
School = x.Key,
Classes = x.GroupBy(y => y.Class).Select(z => new Class
{
Name = z.Key,
Pupils = z.Select(w => w.Pupil).ToList()
}).ToList()
}).ToList();
3 - Example for test :
List<Area> returnedlist = new List<Area>
{
new Area{School = "s1", Class="c1",Pupil="p1"},
new Area{School = "s1", Class="c1",Pupil="p2"},
new Area{School = "s1", Class="c2",Pupil="p1"},
new Area{School = "s1", Class="c2",Pupil="p2"},
new Area{School = "s2", Class="c1",Pupil="p1"},
};
Result
[
{
"School":"s1",
"Classes":[
{
"Name":"c1",
"Pupils":[
"p1",
"p2"
]
},
{
"Name":"c2",
"Pupils":[
"p1",
"p2"
]
}
]
},
{
"School":"s2",
"Classes":[
{
"Name":"c1",
"Pupils":[
"p1"
]
}
]
}
]
Namespaces :
using System.Linq;
using System.Collections.Generic;

Transforming an array of name-value pairs in a JSON array with individual properties in the containing object

I'm trying to transform a JSON array to become object format.
Example:
{
"country": "USA",
"date": "2019-6-30",
"Speaker": [
{
"id": "name",
"value": "Tiger"
},
{
"id": "age",
"value": "35"
},
{
"id": "topic",
"value": ".NET"
}
]
}
I want to convert this to:
{
"country": "USA",
"date": "2019-6-30",
"name": "Tiger",
"age": 35,
"topic": ".NET"
}
I tried several ways, but had no luck. It seems I can't get the value of the internal array. Please help.
You can use Json.Net's LINQ-to-JSON API (JObjects) to transform your JSON:
JObject root = JObject.Parse(json);
JProperty arrayProp = root.Properties()
.Where(jp => jp.Value.Type == JTokenType.Array)
.FirstOrDefault();
if (arrayProp != null)
{
foreach (JObject item in arrayProp.Value.Children<JObject>())
{
root[(string)item["id"]] = item["value"];
}
arrayProp.Remove();
}
json = root.ToString();
This solution does not depend on the array property having any particular name, nor does it care what the item ids are. However, if there are any ids in the array which overlap with an existing property in the root object, the value in the array will replace the one already in the root object. Similarly, if there are any duplicate ids in the array, the last one will "win".
Working demo: https://dotnetfiddle.net/p3WkqN
You only need a couple of classes to deserialise this JSON, for example:
public class Data
{
public string Country { get; set; }
public string Date { get; set; }
// Deserialise the array as a list of 'SpeakerItem'
public List<SpeakerItem> Speaker { get; set; }
// These will throw exceptions if the id doesn't match, but it's a start
public string Name => Speaker.Single(s => s.Id == "name").Value;
public string Age => Speaker.Single(s => s.Id == "age").Value;
public string Topic => Speaker.Single(s => s.Id == "topic").Value;
}
public class SpeakerItem
{
public string Id { get; set; }
public string Value { get; set; }
}
Now you can do something like this:
var value = JsonConvert.DeserializeObject<Data>(json);
I have something like this using JSON.Net, first of all your json is wrong (you have dot in the end of country line). I have used DynamoObjects.
string json = #"
{
""country"": ""USA"",
""date"": ""2019-6-30"",
""Speaker"" : [
{
""id"": ""name"",
""value"": ""Tiger""
},
{
""id"": ""age"",
""value"": ""35""
},
{
""id"": ""topic"",
""value"": "".NET""
},
]
}";
dynamic animalJson = JsonConvert.DeserializeObject<dynamic>(json);
dynamic animal = new ExpandoObject();
animal.country = animalJson.country;
animal.date = animalJson.date;
animal.name = animalJson.Speaker[0].value;
animal.age = animalJson.Speaker[1].value;
animal.topic = animalJson.Speaker[2].value;
string modifiedAnimalJson = JsonConvert.SerializeObject(animal);

Combine two JSON structures into one in C#

We have two similar but slightly different JSON structures. First JSON structure is for No Strata scenario – this is applicable for measureId “001” and “002” . Second JSON structure is for multi strata – this is applicable for "measureId": "003". For each testTIN, we need to combine these two measurements as shown in the expected JSON structure.
JSON 1 – No Strata
{
"testTIN": "123",
"measurements": [{
"measureId": "001",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 5
}
},
{
"measureId": "002",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 6
}
}
]
}
JSON 2 – Multi Strata
{
"testTIN": "123",
"measurements": [
{
"measureId": "003",
"value": {
"strata": [{
"IsEndToEndReported": true,
"PerformanceMet": 5,
"Stratum": "Level1"
},
{
"IsEndToEndReported": true,
"PerformanceMet": 6,
"Stratum": "Level2"
}
]
}
}
]
}
Expected JSON
{
"testTIN": "123",
"measurements": [{
"measureId": "001",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 5
}
},
{
"measureId": "002",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 6
}
},
{
"measureId": "003",
"value": {
"strata": [{
"IsEndToEndReported": true,
"PerformanceMet": 5,
"Stratum": "Level1"
},
{
"IsEndToEndReported": true,
"PerformanceMet": 6,
"Stratum": "Level2"
}
]
}
}
]
}
How to get this new JSON structure combining both the above JSON structures?
C# Code
//No Strata
List<FlattenedRawData> rowList_NoStrata = HelperMethodClasses.GetFlattenedRawData_NoStrata();
List<MeasurementSet__NoStrata> result_NoStrata = rowList_NoStrata.GroupBy(records => records.EffectiveTIN)
.Select(y => new MeasurementSet__NoStrata
{
testTIN = y.Key,
measurements = y.Select(i =>
new Measurement_NoStrata()
{
measureId = i.MeasureID,
value = new QualityMeasureValue_NoStrata
{
IsEndToEndReported = true,
PerformanceMet = i.PerformanceMetCount
}
})
.ToList()
})
.ToList();
//Multi Strata
List<FlattenedRawData> rowList_MultiStrata = HelperMethodClasses.GetFlattenedRawData_MultiStrata();
List<MeasurementSet__MultiStrata> resul_MultiStrata =
rowList_MultiStrata.GroupBy(groupBy1 => groupBy1.EffectiveTIN)
.Select(level1 => new MeasurementSet__MultiStrata
{
testTIN = level1.Key,
measurements = level1.GroupBy(groupBy2 => groupBy2.MeasureID).Select(level2 =>
new Measurement_MultiStrata()
{
measureId = level2.Key,
value = new QualityMeasureValue_MultiStrata()
{
strata = level2.Select(level3 => new Strata
{
IsEndToEndReported = true,
PerformanceMet = level3.PerformanceMetCount,
Stratum = level3.Stratum
}).ToList(),
}
}).ToList()
}).ToList();
string requestJson = Newtonsoft.Json.JsonConvert.SerializeObject(resul_MultiStrata[0]);
public class FlattenedRawData
{
public string EffectiveTIN { get; set; }
public string MeasureID { get; set; }
public int PerformanceMetCount { get; set; }
public string Stratum { get; set; }
}
public class Measurement_NoStrata
{
public string measureId { get; set; }
public QualityMeasureValue_NoStrata value { get; set; }
}
public class Measurement_MultiStrata
{
public string measureId { get; set; }
public QualityMeasureValue_MultiStrata value { get; set; }
}
public class QualityMeasureValue_NoStrata
{
public bool IsEndToEndReported { get; set; }
public int PerformanceMet { get; set; }
}
public class QualityMeasureValue_MultiStrata
{
public List<Strata> strata = new List<Strata>();
}
public class Strata
{
public bool IsEndToEndReported { get; set; }
public int PerformanceMet { get; set; }
public string Stratum { get; set; }
}
public class MeasurementSet__NoStrata
{
public string testTIN { get; set; }
public List<Measurement_NoStrata> measurements { get; set; }
}
public class MeasurementSet__MultiStrata
{
public string category { get; set; }
public string testTIN { get; set; }
public List<Measurement_MultiStrata> measurements { get; set; }
}
Update
Good References:
Newtonsoft Json.Net serialize JObject doesn't ignore nulls, even with the right settings
You can simply do a union (without creating complex POCO classes unless you really need it). Newtonsoft supports merging JSon's:
var dataObject1 = JObject.Parse(#"{
""testTIN"" : ""123"",
""measurements"": [{
""measureId"": ""001"",
""value"": {
""IsEndToEndReported"": true,
""PerformanceMet"": 5
}
},
{
""measureId"": ""002"",
""value"": {
""IsEndToEndReported"": true,
""PerformanceMet"": 6
}
}
]
}");
var dataObject2 = JObject.Parse(#"{
""testTIN"": ""123"",
""measurements"": [
{
""measureId"": ""003"",
""value"": {
""strata"": [{
""IsEndToEndReported"": true,
""PerformanceMet"": 5,
""Stratum"": ""Level1""
},
{
""IsEndToEndReported"": true,
""PerformanceMet"": 6,
""Stratum"": ""Level2""
}
]
}
}
]
}");
dataObject1.Merge(dataObject2, new JsonMergeSettings
{
// union array values together to avoid duplicates
MergeArrayHandling = MergeArrayHandling.Union
});
string json = dataObject1.ToString();
This will give an output:
{
"testTIN": "123",
"measurements": [
{
"measureId": "001",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 5
}
},
{
"measureId": "002",
"value": {
"IsEndToEndReported": true,
"PerformanceMet": 6
}
},
{
"measureId": "003",
"value": {
"strata": [
{
"IsEndToEndReported": true,
"PerformanceMet": 5,
"Stratum": "Level1"
},
{
"IsEndToEndReported": true,
"PerformanceMet": 6,
"Stratum": "Level2"
}
]
}
}
]
}
If your initial No Strata and multi strata measurement results are, in fact, already serialized as JSON structures, you can simply merge them together using JContainer.Merge(Object, JsonMergeSettings) with the merge setting MergeArrayHandling.Concat as follows:
// Get the initial measurement JSON measurements as strings.
IEnumerable<string> measturements = GetJsonMeasurements();
// And concatenate them together into a combined `JObject`:
var settings = new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat };
var json = measturements.Aggregate(new JObject(),
(j, s) => { j.Merge(JObject.Parse(s), settings); return j; });
Here I am assuming the measurements have already been grouped by "testTIN" value. If not, this is easily added by parsing all the results to a JObject and grouping by "testTIN" value before aggregating as follows:
var settings = new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat };
var json = measurements
.Select(j => JObject.Parse(j))
.GroupBy(j => (string)j["testTIN"])
.Select(g => g.Aggregate(new JObject(),
(j1, j2) => { j1.Merge(j2, settings); return j1; })
)
.ToList();
Alternatively, if your JSON results are stored in some collection of files, you can merge directly from the files as follows:
var settings = new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat };
var json = fileNames.Aggregate(new JObject(),
(j, s) =>
{
using (var file = File.OpenText(s))
using (var reader = new JsonTextReader(file))
{
j.Merge(JToken.Load(reader), settings);
}
return j;
});
Demo fiddle with unit tests here.
Update
if you have an enumerable of objects and wish to create a combined JSON file by merging together their JSON representations, you can project each object to a JObject using JObject.FromObject, then merge those:
// Get the results
IEnumerable<MeasurementSet__NoStrata> measurements1 = GetNoStrataMeasurements(); // Get no-strata measurements.
IEnumerable<MeasurementSet__MultiStrata> measurements2 = GetMultiStrataMeasurements(); // Get multistrata measurements.
// Combine them into a single enumerable
IEnumerable<object> measurements = measurements1.Cast<object>()
.Concat(measurements2.Cast<object>());
// Select serialization and merge settings
var serializerSettings = new JsonSerializerSettings
{
// Put whatever you want here, e.g.
NullValueHandling = NullValueHandling.Ignore,
};
var mergeSettings = new JsonMergeSettings
{
// Required
MergeArrayHandling = MergeArrayHandling.Concat,
// Put whatever you want here, either Ignore or Merge
// https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Linq_MergeNullValueHandling.htm
MergeNullValueHandling = MergeNullValueHandling.Ignore, // Or Merge if you prefer
};
// Serialize and merge the results
var serializer = JsonSerializer.CreateDefault(serializerSettings);
var json = measurements
.Select(m => JObject.FromObject(m, serializer))
.GroupBy(j => (string)j["testTIN"])
.Select(g => g.Aggregate(new JObject(),
(j1, j2) => { j1.Merge(j2, mergeSettings); return j1; })
)
// Do we need to remove the `"category"` property?
// If so do it here.
.Select(o => { o.Remove("category"); return o; })
.ToList();
Demo fiddle #2 here.

Convert Json object to List

I have this Json string :
"UserProperties": [
{
"Id": "3", "values": [
{ "prop": "1" }
]
},
{
"Id": "4", "values": [
{ "prop": "1" },
{ "prop": "2" },
{ "prop": "3" }
]
}
]
I Want to convert this into some sort of c# string and list value like this:
public list<int> Id { get; set; }
public list<object> values { get; set; }
public int prop { get; set; }
So that i can manipulate my values :
foreach( int i in Id)
{
foreach( object val in values)
{
var str = i + '-' + val.prop;
}
}
So far what i have done is create a class that will contain those Json string. I get this code from an quite similar approach.
Create a wrapper class
class Wrapper {
[JsonProperty("UserPropertiees")]
public ValueSet valueSet { get; set; }
}
class ValueSet{
[JsonProperty("values")]
public List<string> values {get;set;}
public PropertySet propertySet { get; set; }
}
class PropertySet{
[JsonProperty("property")]
public List<string> property {get;set;}
}
I would use Newtonsoft's Json.NET and deserialize your JSON array into your List.
http://www.newtonsoft.com/json/help/html/SerializingCollections.htm
As per Rob Stewart's advice, NewtonSoft.JSON is the best way to go IMO. Here's something you can put in a console app to have a play with:
string json = #"{""UserProperties"": [
{
""Id"": ""3"", ""values"": [
{ ""prop1"": ""1"" }
]
},
{
""Id"": ""4"", ""values"": [
{ ""prop1"": ""1"" },
{ ""prop2"": ""2"" },
{ ""prop3"": ""3"" }
]
}
]}";
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
Console.WriteLine(o.Id);
}
Console.ReadLine();
EDIT
As per your comments, here is a more complete example. Try this:
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
var sb = new StringBuilder();
sb.Append(o.Id);
sb.Append(":");
bool hasProps = false;
foreach (var value in o.values)
{
if (value.prop1 != null)
{
sb.Append(value.prop1);
sb.Append(',');
hasProps = true;
}
if (value.prop2 != null)
{
sb.Append(value.prop2);
sb.Append(',');
hasProps = true;
}
if (value.prop3 != null)
{
sb.Append(value.prop3);
sb.Append(',');
hasProps = true;
}
}
if (hasProps)
{
sb.Remove(sb.Length - 1, 1); // Remove trailing comma
}
Console.WriteLine(sb.ToString());
}
Console.ReadLine();

Categories

Resources