How can I exclude NULL entries from json. I de-serialize a string into a object list. I can access that null in that list but I want to exclude that null and stored not null rows into the list.
[
{
"transactionId": 1778,
"locName": "IL",
},
{
"transactionId": 1779,
"locName": "NY",
},
{
"transactionId": 1774,
"locName": "IL",
},
{
"transactionId": 1771,
"locName": "NY"
},
null
]
var result = JsonConvert.DeserializeObject<TList>(inputText);
By default, you can't. Consider this code, which instructs JSON.NET to ignore null values:
public static void Main()
{
var json = "[ { \"transactionId\": 1778, \"locName\": \"IL\", }, { \"transactionId\": 1779, \"locName\": \"NY\", }, { \"transactionId\": 1774, \"locName\": \"IL\", }, { \"transactionId\": 1771, \"locName\": \"NY\" }, null ]";
var list = JsonConvert.DeserializeObject<List<Entity>>(json, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
public class Entity
{
public string TransactionId { get; set; }
public string LocName { get; set; }
}
Then list's last entry will still be null, because it's not a null value, it's a null array element.
If you wish to filter those, simply use Linq:
var nonNullList = list.Where(l => l != null).ToList();
Related
I have a .NET 7 web api with a route that returns Users.
public class User
{
public Guid? Id { get; set; }
public String? Name { get; set; }
public Int32? Age { get; set; }
public String? HairColor { get; set; }
}
If "Name" comes in the Select query param, the database only returns the Name property and the Ok(users); returns. Ex:
[
{
"id": null,
"name": "Test1",
"age": null,
"hairColor": null
},
{
"id": null,
"name": "Test2",
"age": null,
"hairColor": null
},
{
"id": null,
"name": "Test3",
"age": null,
"hairColor": null,
}
]
To save on package size, I would instead like it to return:
[
{
"name": "Test1"
},
{
"name": "Test2"
},
{
"name": "Test3"
}
]
But, if nothing comes in the Select query param, all of the properties get populated and returned even if null/default. How can I dynamically set
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
So that if Select is set, Ok(users) ignores the null/default properties when serializing, otherwise it returns all properties?
IMHO the simpliest way is
if (users[0].Id != null) return Ok(users); //or more validations
return Ok( users.Select(u => new { Name = u.Name }) );
I'm not sure if this is the best way, but utilizing anonymous types was a spark. I thought, I would need to dynamically and recursively find all of the properties in Select, then add it to the anonymous type. Instead of reinventing the wheel, why not just utilize System.Text.Json?
if (select is not null && select.Count > 0)
{
var modelsWithLessProperties = JsonSerializer.Deserialize<object>(JsonSerializer.Serialize(models, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
}));
return Ok(modelsWithLessProperties);
}
return Ok(models);
Serializing, then deserializing may not be the most efficient way of doing this, but it is utilizing established code.
I am open to any, more efficient, established (i.e. not error prone) way of doing this.
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"]
});
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);
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.
I am having issues figuring out how to parse a JSON response that I am getting back from a 3rd party. I need to convert from the following JSON (a simplified example of what I am getting back) to a C# class. The main issue is that the columns may be in a different order, have extra fields, or missing ones (Which should just end up as null values).
{
"columns": [
{ "id": { "type": "Numeric", "nullable": false } },
{ "name": { "type": "Text", "nullable": false } },
{ "description": { "type": "Text", "nullable": true } },
{ "last_updated": { "type": "DateTime", "nullable": false } }
],
"rows": [
[1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
[4, "bar", null, "2013-07-01T13:04:24Z"]
]
}
The C# class for this example would be
public class Record
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public DateTime? last_updated { get; set; }
}
I have tried using a custom json converter, but did not have much luck getting it to work with the separated metadata and values. Does anyone have any ideas on how to go about parsing this kind of data? Eventually there will be multiple "Record" types, which is why the response from the server can be dynamic.
Your question is similar to this recent one and can be solved using a similar converter:
public class RowColumnListConverter<T> : JsonConverter
{
const string columnsKey = "columns";
const string rowsKey = "rows";
public override bool CanConvert(Type objectType)
{
if (!typeof(ICollection<T>).IsAssignableFrom(objectType))
return false;
// This converter is only implemented for read/write collections. So no arrays.
if (objectType.IsArray)
return false;
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var list = existingValue as ICollection<T> ?? (ICollection<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var root = JObject.Load(reader);
var rows = root.GetValue(rowsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
if (rows == null)
return list;
var columns = root.GetValue(columnsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
if (columns == null)
throw new JsonSerializationException(columnsKey + " not found.");
var columnNames = columns.Cast<JObject>().SelectMany(o => o.Properties()).Select(p => p.Name).ToArray();
foreach (var row in rows)
{
if (row == null || row.Type == JTokenType.Null)
list.Add(default(T));
else if (row.Type == JTokenType.Array)
{
var o = new JObject(columnNames.Zip(row, (c, r) => new JProperty(c, r)));
list.Add(o.ToObject<T>(serializer));
}
else
throw new JsonSerializationException(string.Format("Row was not an array: {0}", row));
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static TJTOken NullCast<TJTOken>(this JToken token) where TJTOken : JToken
{
if (token == null || token.Type == JTokenType.Null)
return null;
var typedToken = token as TJTOken;
if (typedToken == null)
throw new JsonSerializationException(string.Format("Token {0} was not of type {1}", token.ToString(Formatting.None), typeof(TJTOken)));
return typedToken;
}
}
Then use it like:
var list = JsonConvert.DeserializeObject<List<Record>>(json, new RowColumnListConverter<Record>());
Or
var list = JsonConvert.DeserializeObject<List<Record>>(json, new JsonSerializerSettings
{
Converters = { new RowColumnListConverter<Record>() },
});
The converter works by loading the outer object into a temporary JObject, then reformatting the "columns" and "rows" arrays into a more conventional list of objects for deserialization. Note that no attempt is made to use the type information in the "columns" list, it is simply assumed that the POCO members have the correct type. Also, WriteJson() is not implemented since there isn't enough information in the question to determine how to emit the type information for any possible column type; a full specification would be required.
Sample fiddle.
i think this approach is tricky somehow, but if you have to follow this, you can put order property for them to solve your order problem:
{
"columns": [
{ "id": { "type": "Numeric", "nullable": false, "order":1 } },
{ "name": { "type": "Text", "nullable": false, "order":2 } },
{ "description": { "type": "Text", "nullable": true, "order":3 } },
{ "last_updated": { "type": "DateTime", "nullable": false, "order":4 } }
],
"rows": [
[1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
[4, "bar", null, "2013-07-01T13:04:24Z"]
]
}
also for null value you can get a default value to recognize them and replace with null in your custom json converter.
the best structure for your data can be:
public class Column
{
public string type { get; set; }
public bool nullable { get; set; }
public int order { get; set; }
}
public class Model
{
public List<Dictionary<string, Column>> columns { get; set; }
public List<List<string>> rows { get; set; }
}
you can convert your json to the a Model class directly.