Get nested fieldNames from JSON object - c#

I have a JSON that contains a list of types. These types have some name and some fields.
Fields have a property of dataType and if it's value is object, it refers to a another type. That type could be found by its name specified in referenceType property.
There is also a property named parentType which means the type is a child of the parentType and it contains some additional properties, but have to be considered as an object of parentType only.
I am trying to process this JSON to get all the nested property names for all the types present in the array.
{
"types": [
{
"name": "User1",
"fields": [
{
"name": "name",
"dataType": "string"
},
{
"name": "address",
"dataType": "object",
"referenceType": "Address",
"isArray": true
},
{
"name": "weeklyRoles",
"dataType": "object",
"isArray": true,
"referenceType": "Role"
}
]
},
{
"name": "User2",
"fields": [
{
"name": "name",
"dataType": "string"
},
{
"name": "address",
"dataType": "object",
"referenceType": "Address",
"isArray": true
}
]
},
{
"name": "Address",
"fields": [
{
"name": "AddressLine1",
"dataType": "string"
},
{
"name": "AddressLine2",
"dataType": "string"
}
]
},
{
"name": "BusinessAddress",
"parentType": "Address",
"fields": [
{
"name": "headquarters",
"dataType": "string"
}
]
},
{
"name": "ServiceAddress",
"parentType": "Address",
"fields": [
{
"name": "servicePartner",
"dataType": "string"
},
{
"name": "serviceType",
"dataType": "string"
}
]
},
{
"name": "Role",
"fields": [
{
"name": "roleName",
"dataType": "string"
},
{
"name": "accessCountsObj1",
"dataType": "object",
"referenceType": "Role2"
}
]
},
{
"name": "Role2",
"fields": [
{
"name": "roleName2",
"dataType": "string"
},
{
"name": "accessCountsObj2",
"dataType": "object",
"referenceType": "Role3"
}
]
},
{
"name": "Role3",
"fields": [
{
"name": "roleName3",
"dataType": "string"
},
{
"name": "accessCountsObj3",
"dataType": "object",
"referenceType": "Role4"
}
]
},
{
"name": "Role4",
"fields": [
{
"name": "roleName4",
"dataType": "string"
}
]
}
]
}
Pattern in which I am expecting the result is <typeName>;<fieldName>.<nestedFieldName>
Expected Output
[
"User1;address.AddressLine1",
"User1;address.AddressLine2",
"User1;address.headquarters",
"User1;address.servicePartner",
"User1;address.serviceType",
"User1;weeklyRoles.roleName",
"User1;weeklyRoles.accessCountsObj1.roleName2",
"User1;weeklyRoles.accessCountsObj1.accessCountsObj2.roleName3",
"User1;weeklyRoles.accessCountsObj1.accessCountsObj2.accessCountsObj3.roleName4",
"User2;address.AddressLine1",
"User2;address.AddressLine2",
"User2;address.headquarters",
"User2;address.servicePartner",
"User2;address.serviceType",
"Role;accessCountsObj1.roleName2",
"Role;accessCountsObj1.accessCountsObj2.roleName3",
"Role;accessCountsObj1.accessCountsObj2.accessCountsObj3.roleName4",
"Role2;accessCountsObj2.roleName3",
"Role2;accessCountsObj2.accessCountsObj3.roleName4",
]
I have tried to write a recursive function to process it but it is not giving me the expected result and also does not have terminating condition.
public IList<string> GetKeys(JArray types, string parentType = null)
{
var nestedKeys = new List<string>();
foreach (var type in types)
{
var fields = type[Constants.Fields].ToObject<List<JObject>>();
var typeName = type.Value<string>("name");
var nestedKeyBuilder = new StringBuilder($"{typeName};");
if (!string.IsNullOrEmpty(parentType))
{
nestedKeyBuilder = new StringBuilder($"{parentType};");
}
foreach (var field in fields)
{
var datatype = field.Value<string>("dataType");
if (string.Equals(datatype,"object"))
{
var fieldName = field.Value<string>("name");
var referenceTypeName = field.Value<string>("referenceType");
var referenceTypeObject = types.Where(t => string.Equals(t.Value<string>("name"), referenceTypeName))?.First();
if (referenceTypeObject != null)
{
var refTypeFields = referenceTypeObject["fields"].ToObject<List<JObject>>();
foreach (var refTypeField in refTypeFields)
{
var refTypeFieldName = refTypeField.Value<string>("name");
var refTypeDataType = refTypeField.Value<string>("dataType");
var refTypeReferenceTypeName = refTypeField.Value<string>("referenceType");
if (string.Equals(refTypeDataType, "object") && string.Equals(refTypeReferenceTypeName, currentReferenceType))
{
var refTypeNestedKeys = GetKeys(types, typeName);
nestedKeys.AddRange(refTypeNestedKeys);
}
else
{
nestedKeyBuilder.Append($"{fieldName}.{refTypeFieldName}");
nestedKeys.Add(nestedKeyBuilder.ToString());
}
}
}
}
}
}
return nestedKeys;
}

There is a NuGet that you can use to handle JSON files.
Search for Newtonsoft.Json
https://www.newtonsoft.com/json
All your problems should be solved with that

You can use Newtonsoft library to do this.
This is your POCO class.
{
public string name { get; set; }
public string dataType { get; set; }
public string referenceType { get; set; }
public bool? isArray { get; set; }
}
public class Type
{
public string name { get; set; }
public List<Field> fields { get; set; }
public string parentType { get; set; }
}
public class RootObject
{
public List<Type> types { get; set; }
}
and write the function to DeserializeObject
[TestMethod]
public void Read()
{
var sample1 = #"X:\JsonFilePath\data.json";
var jsonString=File.ReadAllText(sample1);
var result =JsonConvert.DeserializeObject<RootObject>(jsonString);
Assert.IsNotNull(result);
}

Use newtonsoft JSON to Deseriliaze the JSON string.
JSON.Deserialize<IEnumerable<Type>>(jsonString);
This should give you an IEnumerable to process it however you want. Types is the class you create with the JSON properties as class properties. You can use something like this
NOTE: Type and Field are very vague names. Type is a c# class and therefore you should not use it to create a class of your own. You will face ambiguous errors and you will need to qualify the namespace in full to use your class. Strongly suggesting to name it something else and use the attributes to map it to the JSON string.
[JsonObject(Title="People")]
public class Type
{
[JsonProperty("name")]
string Name{ get; set; }
[JsonProperty("fields")]
Field FieldValue[]{ get; set; }
}

Related

Filtering Nested array with linq

I have an array like this coming from API response and I want to filter by nested property products to returns product with only id 11
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
},
{
"id": "14",
"status": null
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
},
{
"id": "28",
"status": null
}
]
},
{
"name": "123",
"products": [
{
"id": "48",
"status": null
}
]
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
the resulting output should look like this,
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "123",
"products": []
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
I'm trying to return filter output with the C# LINQ method. but, getting an error when filtering a nested array with the following method
"Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type"
assets.Select(asset => asset.products.Where(product => product.id == "11")).ToArray();
You can just remove all items from every products array, whose id doesn't equal 11. Parse JSON to JObject, iterate over assets array, find products with id different from 11 and remove them form original array
var json = JObject.Parse(jsonString);
foreach (var asset in json["assets"])
{
var productsArray = asset["products"] as JArray;
if (productsArray == null)
continue;
var productsToRemove = productsArray
.Where(o => o?["id"]?.Value<int>() != 11)
.ToList();
foreach (var product in productsToRemove)
productsArray.Remove(product);
}
Console.WriteLine(json);
Instead of Select and Applying a, Where try to remove the Products that you do not want.
Following is a sample code.
class Program
{
static void Main(string[] args)
{
var json = #"
{
'assets': [
{
'name': 'abc',
'products': [
{
'id': '11',
'status': true
},
{
'id': '14',
'status': null
}
]
},
{
'name': 'xyz',
'products': [
{
'id': '11',
'status': true
},
{
'id': '28',
'status': null
}
]
},
{
'name': '123',
'products': [
{
'id': '48',
'status': null
}
]
},
{
'name': '456',
'products': [
{
'id': '11',
'status': true
}
]
}
]}";
var root = JsonConvert.DeserializeObject<Root>(json);
var assets = root.Assets;
assets.ForEach(a =>
{
a.Products.RemoveAll(p => p.Id != 11);
});
}
}
public partial class Root
{
[JsonProperty("assets")]
public List<Asset> Assets { get; set; }
}
public partial class Asset
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("products")]
public List<Product> Products { get; set; }
}
public partial class Product
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("status")]
public bool? Status { get; set; }
}
To filter the list, you will need to create a new list with products whose ID == YourId. Following linq query does the necessary steps to create the list you want.
Filter out any assets that dont have any product with ID == 11. This is necessary to skip nulls in the new list that will be generated (Where statement)
Create a collection / list of new assets that only have the products whose ID == 11 (Select statement)
var list = assets.Where(x => x.Products.Where(y => y.Id.Equals("11")).Count() > 0)
.Select(asset => {
return new Asset() {
Name = asset.Name,
Products = asset.Products.Where(x => x.Id == "11").ToList()
};
}).ToList();
// Create the RootObject that holds a list of all the arrays if you want.
Rootobject newAssetCollection = new Rootobject() { Assets = list };
Below is the Json that was printed
Console.WriteLine(JsonConvert.SerializeObject(newAssetCollection, Formatting.Indented));
{
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
}

Validation of an invalid json data with Schema not failing with Deserialization

I am parsing the following JSON:
{"names":{"organizationNames":[{"name":"apple"}]}}
into the schema defined in C# Code as shown below.
public class QueryJson
{
#region Properties
[JsonProperty("organization")]
public HeOrganization Organization { get; set; }
[JsonProperty("names")]
public HeName Names { get; set; }
[JsonProperty("emails")]
public List<HeEmailAddress> Emails { get; set; }
#endregion
#region Linked Classes
public class HeOrganization
{
[JsonProperty("id")]
public Guid? ID { get; set; }
}
public class HeName
{
[JsonProperty("organizationNames")]
[Required(ErrorMessage = "Organization Name is Missing")]
public List<HeOrganizationName> OrganizationName { get; set; }
public class HeOrganizationName
{
[JsonProperty("name")]
[Required(ErrorMessage = "Name is Missing")]
public string Name { get; set; }
}
}
public class HeEmailAddress
{
[JsonProperty("address")]
[Required]
[EmailAddress]
public string Address { get; set; }
}
#endregion
}
If I were to pass an obviously invalid JSON:
{"names":{"organizationNames":[{"user":"apple"}]}}
I was expecting DeserializeObject() to fail or throw an Error, but instead it simply assigns 'Name' to null.
var myJson = JsonConvert.DeserializeObject<T>(jsonFilter);
Where T is the instance of the class.
Any suggestion on how to perform such Validations?
You could use Newtonsoft.Json.Schema to validate the schema of any given Json.
For example, for the class structure you have defined, an equivalent Schema could be generated as
var generator = new JSchemaGenerator();
var schema = generator.Generate(typeof(QueryJson));
The Generated Schema is as follows
{
"definitions": {
"HeEmailAddress": {
"type": [
"object",
"null"
],
"properties": {
"address": {
"type": "string",
"format": "email"
}
},
"required": [
"address"
]
},
"HeName": {
"type": [
"object",
"null"
],
"properties": {
"organizationNames": {
"type": "array",
"items": {
"$ref": "#/definitions/HeOrganizationName"
}
}
},
"required": [
"organizationNames"
]
},
"HeOrganization": {
"type": [
"object",
"null"
],
"properties": {
"id": {
"type": [
"string",
"null"
]
}
},
"required": [
"id"
]
},
"HeOrganizationName": {
"type": [
"object",
"null"
],
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
]
}
},
"type": "object",
"properties": {
"organization": {
"$ref": "#/definitions/HeOrganization"
},
"names": {
"$ref": "#/definitions/HeName"
},
"emails": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/HeEmailAddress"
}
}
},
"required": [
"organization",
"names",
"emails"
]
}
You could now use the Schema to validate your jsons. For example, for the sample invalid json provided in OP
var invalidJson = #"{""names"":{""organizationNames"":[{""user"":""apple""}]}}";
var jsonInstance = JObject.Parse(invalidJson);
bool valid = jsonInstance.IsValid(schema); // False

Dictionary<string, JToken> recursive search

I have DeserializeObject to a C# object however I have objects with dynamic object names so I have structured it like this:
public class RootObject
{
public string name { get; set; }
public TableLayout table{ get; set; }
}
public class TableLayout
{
public Attributes attributes { get; set; } //Static
public Info info { get; set; } //Static
[JsonExtensionData]
public Dictionary<string, JToken> item { get; set; }
}
So basically any dynamic objects that appear will be added to the dictionary and using JsonExtensionData will populate the rest of the property without creating the object classes. Here is my json:
string json = #"
{
"name": "Table 100",
"table": {
"attributes ": {
"id": "attributes",
"type": "attributes"
},
"info": {
"id": "info",
"type": "info"
},
"item-id12": {
"id": "item-id12",
"type": "Row"
"index": 0
},
"item-id16": {
"id": "item-id16",
"type": "Column"
"parentid": "item-id12"
},
"item-id21": {
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
}
}";
How can I use type ="row" and index value(increments to index 1 to evaluate next row) property to get all columns using parentId of column objects in my Dictionary.
Desired Output:
"item-id12": {
"id": "item-id12",
"type": "Row"
"index": 0
},
"item-id16": {
"id": "item-id16",
"type": "Column"
"parentid": "item-id12"
},
"item-id21": {
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
You can use linq to find your root object
var rootNode = json.table.item.Values
.FirstOrDefault(x => x["type"].Value<string>() == "Row" && x["index"].Value<int>() == 0);
if (rootNode == null)
return; // no such item
Now if this item exists use linq again and get all items from dictionary:
var childNodes = json.table.item.Values
.Where(x => x["parentid"]?.Value<string>() == rootNode["id"].Value<string>());
Next code
var output = new[] {rootNode}.Concat(childNodes);
foreach (var item in output)
Console.WriteLine(item);
will print
{
"id": "item-id12",
"type": "Row",
"index": 0
}
{
"id": "item-id16",
"type": "Column",
"parentid": "item-id12"
}
{
"id": "item-id21",
"type": "Column",
"parentid": "item-id12"
}
P.S. Your input json is invalid, it missing few commas

Form the JSON object - serialization C#

"fields": [
{
"field": {
"name": "SMS",
"value": "Yes"
}
},
{
"field": {
"name": "Email",
"value": ""
}
},
{
"field": {
"name": "Total",
"value": ""
}
},
]
I have tried to form the JSON format like above, so i formed the class like below. While serialization it does not return expected form, how can i achieve this one.
public class Test
{
public List<Field> fields;
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}
Response:
"fields": [{
"name": "SMS",
"value": "Yes"
}, {
"name": "Email",
"value": ""
},{
"name": "Total",
"value": ""
}]
Use this website http://json2csharp.com and generate all the classes automatically. Just copy-paste your json there.
You can customize resulting JSON object with anonymous types and LINQ. Please try this code:
var test = new Test {fields = new List<Field>()};
test.fields.Add(new Field {name = "f1", value = "v1"});
test.fields.Add(new Field {name = "f2", value = "v2"});
var json = JObject.FromObject(new { fields = test.fields.Select(f => new {field = f}).ToArray() })
.ToString();
A json variable would be:
{
"fields": [
{
"field": {
"name": "f1",
"value": "v1"
}
},
{
"field": {
"name": "f2",
"value": "v2"
}
}
]
}
You just missed a class level:
public class Test
{
public List<FieldHolder> fields;
}
public class FieldHolder
{
public Field field { get; set; }
}
public class Field
{
public string name { get; set; }
public string value { get; set; }
}

Using C# Nest API to get nested json data is not retrieving data

I have the following json coming through the elasticsearch:
{
"_index": "data-2016-01-14",
"_type": "type-data",
"_id": "AVJBBNG-TE8FYIA1rf1p",
"_score": 1,
"_source": {
"#message": {
"timestamp": 1452789770326461200,
"eventID": 1452789770326461200,
"eventName": "New",
"Price": "38.34",
"Qty": 100,
"statistic_LatencyValue_ns": 1142470,
"statistic_LatencyViolation": false,
"statistic_LossViolation": false
},
"#timestamp": "2016-01-14T16:42:50.326Z",
"#fields": {
"timestamp": "1452789770326"
}
},
"fields": {
"#timestamp": [
1452789770326
]
}
}
I'm using Nest to try to get the eventName data i created the class and marked the property:
public class ElasticTest
{
[ElasticProperty(Type = FieldType.Nested)]
public string eventName { get; set; }
}
But the following query is returning 0 results, what am i doing wrong?
var result = client.Search<CorvilTest>(s => s
.From(0)
.Size(10000)
.Query(x => x
.Term(e => e.eventName,"New"))
);
var r = result.Documents;
Mapping definition:
{
"data-2016-01-14": {
"mappings": {
"type-data": {
"properties": {
"#fields": {
"properties": {
"timestamp": {
"type": "string"
}
}
},
"#message": {
"properties": {
"OrderQty": {
"type": "long"
},
"Price": {
"type": "string"
},
"eventID": {
"type": "long"
},
"eventName": {
"type": "string"
},
"statistic_LatencyValue_ns": {
"type": "long"
},
"statistic_LatencyViolation": {
"type": "boolean"
},
"statistic_LossViolation": {
"type": "boolean"
},
"timestamp": {
"type": "long"
}
}
},
"#timestamp": {
"type": "date",
"format": "dateOptionalTime"
}
}
}
}
}
}
I see that the field #message.eventName is using a standard analyzer which means that its value is lower-cased and split at word boundaries before indexing. Hence the value "new" is indexed and not "New". Read more about it here. You need to be mindful about this fact when using a Term Query. Another thing is that the field eventName is not of nested type. So the code below should work for you.
var result = client.Search<CorvilTest>(s => s
.From(0)
.Size(10000)
.Query(x => x
.Term(e => e.Message.EventName, "new"))); // Notice I've used "new" and not "New"
var r = result.Documents;
For the above code to work the definition of CorvilTest class should be something like below:
public class CorvilTest
{
[ElasticProperty(Name = "#message")]
public Message Message { get; set; }
/* Other properties if any */
}
public class Message
{
[ElasticProperty(Name = "eventName")]
public string EventName { get; set; }
}

Categories