Read JSON output from AppInsights in C# - c#

I want to read AppInsights API output in C# console application.
WebClient wc = new WebClient();
wc.BaseAddress = "https://api.applicationinsights.io/v1/apps/AppInsighID/query?query=requests|where timestamp>= ago(1h)|limit 100";
wc.Headers.Add("Host", "api.applicationinsights.io");
wc.Headers.Add("x-api-key", "key");
string json = wc.DownloadString("");
JObject jsonObject = JObject.Parse(json);
//With this, i got values for Rows
var rowsObject = jsonObject["tables"][0]["rows"];
Now values are in array, under rowObject, so how to read this?
I would also like to know the best practice we should follow when reading json string
I can see data like this
{
"tables": [
{
"name": "PrimaryResult",
"columns": [
{
"name": "timestamp",
"type": "datetime"
},
{
"name": "id",
"type": "string"
},
{
"name": "source",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "url",
"type": "string"
},
{
"name": "success",
"type": "string"
},
{
"name": "resultCode",
"type": "string"
},
{
"name": "duration",
"type": "real"
},
{
"name": "performanceBucket",
"type": "string"
},
{
"name": "itemType",
"type": "string"
},
{
"name": "customDimensions",
"type": "dynamic"
},
{
"name": "customMeasurements",
"type": "dynamic"
},
{
"name": "operation_Name",
"type": "string"
},
{
"name": "operation_Id",
"type": "string"
},
{
"name": "operation_ParentId",
"type": "string"
},
{
"name": "operation_SyntheticSource",
"type": "string"
},
{
"name": "session_Id",
"type": "string"
},
{
"name": "user_Id",
"type": "string"
},
{
"name": "user_AuthenticatedId",
"type": "string"
},
{
"name": "user_AccountId",
"type": "string"
},
{
"name": "application_Version",
"type": "string"
},
{
"name": "client_Type",
"type": "string"
},
{
"name": "client_Model",
"type": "string"
},
{
"name": "client_OS",
"type": "string"
},
{
"name": "client_IP",
"type": "string"
},
{
"name": "client_City",
"type": "string"
},
{
"name": "client_StateOrProvince",
"type": "string"
},
{
"name": "client_CountryOrRegion",
"type": "string"
},
{
"name": "client_Browser",
"type": "string"
},
{
"name": "cloud_RoleName",
"type": "string"
},
{
"name": "cloud_RoleInstance",
"type": "string"
},
{
"name": "appId",
"type": "string"
},
{
"name": "appName",
"type": "string"
},
{
"name": "iKey",
"type": "string"
},
{
"name": "sdkVersion",
"type": "string"
},
{
"name": "itemId",
"type": "string"
},
{
"name": "itemCount",
"type": "int"
}
],
"rows": [
[
"2020-01-16T07:07:35.8423912Z",
"ID",
"",
"POST ",
"https://",
"True",
"200",
57.679,
"<250ms",
"request",
"{\"Product Name\":\"Name\",\"Subscription Name\":\"Name\",\"Operation Name\":\"AdvancedSearch\",\"ApimanagementRegion\":\"Region\",\"ApimanagementServiceName\":\"Name\",\"Apim Request Id\":\"ID\",\"Request-Body\":\"{\\\"P1\\\":25,\\\"P2\\\":1,\\\"P3\\\":\\\"All \\\",\\\"P4\\\":\\\"Earliest\\\",\\\"P5\\\":\\\"Extended\\\",\\\"P6\\\":\\\"All \\\",\\\"P6\\\":\\\"Latest\\\",\\\"queryList\\\":[{\\\"P7\\\":\\\"physics\\\",\\\"P8\\\":\\\"A1\\\",\\\"operator\\\":\\\"\\\"}]}\",\"Cache\":\"None\",\"P9\":\"195.43.22.145\",\"API Name\":\"Name\",\"HTTP Method\":\"POST\"}",
"{\"Response Size\":776,\"Request Size\":1092,\"Client Time (in ms)\":0}",
"POST ",
"ID",
"ID",
"",
"",
"",
"1",
"",
"",
"PC",
"",
"",
"0.0.0.0",
"Milan",
"Milan",
"Italy",
"",
"Value1",
"Value2",
"ID1",
"AppInsight Name",
"Name",
"apim:0.12.885.0",
"ID",
1
]
]
}
]
}

You could deserialize the Json and fetch the Rows information. For example,
var result = JsonConvert.DeserializeObject<RootObject>(str);
var rowData = result.tables.SelectMany(x=>x.rows.SelectMany(c=>c));
If you do not want to Flatten the results, you could use
var rowData = result.tables.SelectMany(x=>x.rows.Select(c=>c));
Where RootObject is defined as
public class Column
{
public string name { get; set; }
public string type { get; set; }
}
public class Table
{
public string name { get; set; }
public List<Column> columns { get; set; }
public List<List<string>> rows { get; set; }
}
public class RootObject
{
public List<Table> tables { get; set; }
}
If you intend to eliminate empty values, you could filter them out using Linq
var rowData = result.tables.SelectMany(x=>x.rows.SelectMany(c=>c))
.Where(x=>!string.IsNullOrEmpty(x));
Or for non-Flatten results
var rowData = result.tables.SelectMany(x=>x.rows.Select(c=>c))
.Where(x=>!string.IsNullOrEmpty(x.ToString()));
Update
Based on comment, to retrieve the information and parse it to a Dto based on the position of values in array, you could do the following. The attribute could be used for handling inline Json Properties as well, as mentioned in the comment.
You could begin by defining an attribute as following.
public class DtoDefinitionAttribute:Attribute
{
public DtoDefinitionAttribute(int order)=>Order = order;
public DtoDefinitionAttribute(int order,bool isJson,Type jsonDataType)
{
Order = order;
JsonDataType = jsonDataType;
IsJson = isJson;
}
public bool IsJson{get;} = false;
public int Order{get;}
public Type JsonDataType {get;}
}
And, then you could decorate your Dto properties with the index of corresponding value in the array. Additionally, in case of Json string expected as Json, then you could use the attribute to indicate it as well, as shown in the ClientTime property For example,
public class Dto
{
[DtoDefinition(0)]
public DateTime CurrentDate{get;set;}
[DtoDefinition(1)]
public string ID{get;set;}
[DtoDefinition(2)]
public string Url{get;set;}
[DtoDefinition(11,true,typeof(Response))]
public Response Json1{get;set;}
}
public class Response
{
[JsonProperty("Response Size")]
public string ResponseSize{get;set;}
[JsonProperty("Request Size")]
public string RequestSize{get;set;}
[JsonProperty("Client Time (in ms)")]
public int ClientTime{get;set;}
}
Now you could use the rowData result obtained using
var listDto = new List<Dto>();
foreach(var row in rowData)
{
listDto.Add(AssignValues(row));
}
Where AssignValues are defined as
public Dto AssignValues(List<string> row)
{
var dto = new Dto();
var properties = typeof(Dto).GetProperties().Where(x=>x.GetCustomAttributes<DtoDefinitionAttribute>().Any());
foreach(var property in properties)
{
var attribute = property.GetCustomAttribute<DtoDefinitionAttribute>();
if(attribute.IsJson)
{
var jsonData = row[attribute.Order].ToString();
var deserializedData = JsonConvert.DeserializeObject(jsonData,attribute.JsonDataType);
property.SetValue(dto,deserializedData);
}
else
{
property.SetValue(dto,Convert.ChangeType(row[attribute.Order],property.PropertyType));
}
}
return dto;
}
The AssignValues method uses reflection to read the Attributes and create an instance of Dto based on it. In case, it finds the attribute defines it as Json, then it would deserialize the json value and use the result.
Demo Code

Related

Ignore base class properties during auto mapping in ElasticSearch using NEST

I'm working on Elastic Search 5.4 and using NEST to create index in C#, When I do a automap for a class which in Inheriting from SortedDictionary, The base class properties like Count,Comparer,Items are added to the Mapping, Can some one please help me how to avoid this.
Thanks
public class Document : SortedDictionary<string, dynamic>
{
public int Id { get; set; }
}
The actual mapping I'm getting is
{"DocumentMapping": {
"mappings": {
"Document": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": null,
"Documents": {
"properties": {
"Comparer": {
"type": "object"
},
"Count": {
"type": "integer"
},
"Item": {
"type": "object",
"include_in_all": false
},
"ID": {
"type": "int "
}
}
}
}
}}}
What I need is
{"DocumentMapping": {
"mappings": {
"Document": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": null,
"Documents": {
"properties":
"ID": {
"type": "int "
}
}
}
}
}}}
How do I ignore the the Comparer,Count and Item from my mapping JSON.

Normalise JSON for flexible WebAPI response

I have a WebAPI method that returns Json in a flexible structure that depends on the request.
Part of the problem is that there could be any number of columns, and they could be any type. The 2 given below (Code and Count) are just one example.
This structure is based on the underlying classes but there could be any number of columns in the output. So, rather than the usual properties you might expect, these are objects in a collection with Name and Value properties.
The downside of this flexible approach is that it gives a non-standard format.
Is there a way to transform this into a more normalised shape? Are there maybe some attributes I can add to the class properties to change the way they are serialised?
For example, where there are 2 columns - Code (string) and Count (numeric):
Current Json:
{
"Rows": [
{
"Columns": [
{
"Value": "1",
"Name": "Code"
},
{
"Value": 13,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "2",
"Name": "Code"
},
{
"Value": 12,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "9",
"Name": "Code"
},
{
"Value": 1,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "5",
"Name": "Code"
},
{
"Value": 2,
"Name": "Count"
}
]
}
]
}
Ideally I'd like to transform it to this:
{
"Rows": [
{
"Code": "1",
"Count": 13
},
{
"Code": "2",
"Count": 12
},
{
"Code": "9",
"Count": 1
},
{
"Code": "5",
"Count": 2
}
]
}
The controller method (C#)
public ReportResponse Get(ReportRequest request)
{
var result = ReportLogic.GetReport(request);
return result;
}
The output classes
public class ReportResponse
{
public List<ReportRow> Rows { get; set; }
public ReportResponse()
{
Rows = new List<ReportRow>();
}
}
public class ReportRow
{
public List<ReportColumn> Columns { get; set; }
public ReportRow()
{
Columns = new List<ReportColumn>();
}
}
public class ReportColumn<T> : ReportColumn
{
public T Value { get; set; }
public ReportColumn(string name)
{
Name = name;
}
}
public abstract class ReportColumn
{
public string Name { get; internal set; }
}
I think the easiest way would be to map your class to a dictionary before serializing. Something like:
var dictionaries = List<Dictionary<string, object>();
foreach(var column in rows.Columns)
{
dictionaries.Add(new Dictionary<string, object>{{column.Name, column.Value}});
}
Then serialize the dictionaries variable should do the trick.
If you're using the output in JavaScript, you could translate as follows:
var
data = {
"Rows": [
{
"Columns": [
{
"Value": "1",
"Name": "Code"
},
{
"Value": 13,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "2",
"Name": "Code"
},
{
"Value": 12,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "9",
"Name": "Code"
},
{
"Value": 1,
"Name": "Count"
}
]
},
{
"Columns": [
{
"Value": "5",
"Name": "Code"
},
{
"Value": 2,
"Name": "Count"
}
]
}
]
},
output = [
];
data.Rows.forEach(function (row)
{
var
newRow = {};
row.Columns.forEach(function (column)
{
newRow[column.Name] = column.Value;
});
output.push(newRow);
})
console.log(JSON.stringify(output));

parsing JSON format file in c#

Greeting,
I am finding difficulty in parsing a JSON format file in c# having an array of highly nested objects which looks as follows
[
{
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" },
{ "id": "1002", "type": "Chocolate" },
{ "id": "1003", "type": "Blueberry" },
{ "id": "1004", "type": "Devil's Food" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5005", "type": "Sugar" },
{ "id": "5007", "type": "Powdered Sugar" },
{ "id": "5006", "type": "Chocolate with Sprinkles" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
},
{
"id": "0002",
"type": "donut",
"name": "Raised",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5005", "type": "Sugar" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
},
{
"id": "0003",
"type": "donut",
"name": "Old Fashioned",
"ppu": 0.55,
"batters":
{
"batter":
[
{ "id": "1001", "type": "Regular" },
{ "id": "1002", "type": "Chocolate" }
]
},
"topping":
[
{ "id": "5001", "type": "None" },
{ "id": "5002", "type": "Glazed" },
{ "id": "5003", "type": "Chocolate" },
{ "id": "5004", "type": "Maple" }
]
}
]
I am looking for a solution like
"id", "type","name", "ppu" as private members of a class and "batters" and "topping" as dictionary members.
Kindly suggest me the better way in getting it parsed.
Thank you.
Following class structure will help you to parse JSON to C# object.
public class Batter
{
public string id { get; set; }
public string type { get; set; }
}
public class Batters
{
public List<Batter> batter { get; set; }
}
public class Topping
{
public string id { get; set; }
public string type { get; set; }
}
public class RootObject
{
public string id { get; set; }
public string type { get; set; }
public string name { get; set; }
public double ppu { get; set; }
public Batters batters { get; set; }
public List<Topping> topping { get; set; }
}

can not search elasticsearch based on document property values

I have lots of product typed documents saved in elasticsearch but couldn't search by documents property values and need help.
//Product Class
public Guid productID { get; set; }
public string productName { get; set; }
public Guid productTypeID { get; set; }
public List<Guid> categoryIds {get; set; }
I'm trying to search like this:
//Search function
var esconn = Yaziylabir.Bukalemun.DataObjects.ElasticSearchConnectionManager.GetClient();
QueryContainer q = null;
if (!ProductType.HasValue || (ProductType.HasValue && ProductType.Value == B2CVariables.PRODUCTTYPE_PRODUCT))
{
q = Query<ProductModel>.Term(t => t.ProductTypeID, B2CVariables.PRODUCTTYPE_PRODUCT);
}
if (Category != null)
{
//catListZ is also List<Guid>
q &= Query<ProductModel>.Terms(u=>u.Field(z=>z.CategoryIDs).Terms<Guid>(catListZ));
}
// as a bonus I also have keyword search
if (!string.IsNullOrEmpty(Keyword))
{
q &= Query<ProductModel>.QueryString(t => t.Query(Keyword));
}
//do search
var pp = new SearchRequest(Yaziylabir.Bukalemun.DataObjects.ElasticSearchConnectionManager.DefaultIndex, "product");
pp.Query = q;
pp.Size = PageSize;
var res = esconn.Search<ProductModel>(pp);
rtnVal = res.Documents.ToList();
Now, I tried combinations of these (only producttypeID, only categoryIDs, only keyword, etc...) and watch what is happening with fiddler.
No result comes back, no errors are raised. Only 0 hits. Request body seems ok too.
When I check documents stored in that index, they are there and they have values required and should return in result.
What is wrong here? Do you have any ideas? Please help me here. I'm feeling ashamed to be the guy who couldn't search a database properly.
EDIT:
Search's body text:
{"size":12,"query":{"term":{"productTypeID":{"value":"cae344cf-8cfa-4960-8387-8ee89899c53f"}}}}
Example document:
{
"productID": "5687b8ac-c3fe-4f1a-9643-08b0bf6cede8",
"productName": "7011 Yelek",
"productCode": "701102Y001 ",
"currency": {
"currencyID": 1,
"sign": "TL",
"rate": 0
},
"normalPrice": 170,
"currentPrice": 84.9,
"isDiscounted": true,
"taxRate": 8,
"productTypeID": "cae344cf-8cfa-4960-8387-8ee89899c53f",
"defaultImagePath": "/contents/images/productimages/75681ee4-19b3-4c7d-a24b-b3566085a980.jpg",
"totalStockCount": 8,
"totalStockRecordCount": 4,
"isInStock": true,
"statusID": "9ad17471-2ff2-4eb0-9cb0-4b86922263ea",
"categoryIDs": [
"a8c83f54-b784-4866-89c3-cabc641490d5",
"9d5a9ab7-8edb-4d5a-800b-c48bf6575d78"
]
}
I didn't include all properties because it will make document very long.
Here is the mapping:
{
"mappings": {
"product": {
"properties": {
"categoryIDs": {
"type": "string"
},
"currentPrice": {
"type": "double"
},
"isDiscounted": {
"type": "boolean"
},
"isInStock": {
"type": "boolean"
},
"normalPrice": {
"type": "double"
},
"productCode": {
"type": "string"
},
"productID": {
"type": "string"
},
"productName": {
"type": "string"
},
"productTypeID": {
"type": "string"
},
"statusID": {
"type": "string"
},
"taxRate": {
"type": "double"
},
"totalStockCount": {
"type": "long"
},
"totalStockRecordCount": {
"type": "long"
}
}
}
}
}
I suspect the productTypeID field is using either the default analyzer - standard - or any other analyzer that's splitting it in the wrong places. What you need is for productTypeID to be index: not_analyzed or analyzed with something like keyword. And you need to create the mapping manually, otherwise you can't do it the way you want it.
The idea is that ES is tokenizing by default your productTypeID values and will split them at -, so in the index you'd have tokens not the entire value. You, instead, need to have that value unchanged so that term will match it fully.
For example, not to mess up with your previous mapping, you can add fields to define a sub-field that will be not_analyzed:
"productTypeID": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}
And your query needs to change slightly: {"size":12,"query":{"term":{"productTypeID.raw":{"value":"cae344cf-8cfa-4960-8387-8ee89899c53f"}}}}
For completeness sake, this is the complete command to create the mapping by hand. You could do it while the index is still "alive", but you'd still have to re-index the documents:
curl -XPUT "http://localhost:9200/your_index" -d'
{
"mappings": {
"product": {
"properties": {
"categoryIDs": {
"type": "string"
},
"currentPrice": {
"type": "double"
},
"isDiscounted": {
"type": "boolean"
},
"isInStock": {
"type": "boolean"
},
"normalPrice": {
"type": "double"
},
"productCode": {
"type": "string"
},
"productID": {
"type": "string"
},
"productName": {
"type": "string"
},
"productTypeID": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
},
"statusID": {
"type": "string"
},
"taxRate": {
"type": "double"
},
"totalStockCount": {
"type": "long"
},
"totalStockRecordCount": {
"type": "long"
}
}
}
}
}'

Query JSON using LINQ

I have a Json response that I receive from an API call. It has several nested levels as show below (this is a snippet):
"Items": [
{
"Result": {
"Id": "191e24b8-887d-e111-96ec-000c29128cee",
"Name": "Name",
"StartDate": "2012-04-03T00:00:00+01:00",
"EndDate": null,
"Status": {
"Name": "Active",
"Value": 5
},
"Client": {
"Id": "35ea10da-b8d5-4ef8-bf23-c829ae90fe60",
"Name": "client Name",
"AdditionalItems": {}
},
"ServiceAgreement": {
"Id": "65216699-a409-44b0-8294-0e995eb05d9d",
"Name": "Name",
"AdditionalItems": {
"ScheduleBased": true,
"PayFrequency": {
"Id": "981acb72-8291-de11-98fa-005056c00008",
"Name": "Weekly",
"AdditionalItems": {}
},
"PayCycle": [
{
"Name": "Schedule Based",
"ScheduleBased": true,
"SelfBilling": false,
"Id": "a8a2ecc4-ff79-46da-a135-743b57808ec3",
"CreatedOn": "2011-09-16T23:32:19+01:00",
"CreatedBy": "System Administrator",
"ModifiedOn": "2011-09-16T23:32:19+01:00",
"ModifiedBy": "System Administrator",
"Archived": false
}
]
}
},
}
]
...
What I want to do is retreive the data from the PayCycle node using Linq. I can for example get the items with a value of true using Result.ServiceAgreement.AdditionalItems.SchedultedBased using the following Linq in the Controller:
var result = from p in data["Data"]["Items"].Children()
where (bool)p["Result"]["ServiceAgreement"]["AdditionalItems"]["ScheduleBased"] == true
select new
{
Name = (string)p["Result"]["Client"]["Name"],
Id = (string)p["Result"]["Client"]["Id"]
};
Now I need to get Result.ServiceAgreement.AdditionalItems.Paycycle.ScheduleBased and SelfBilling properties. How do I do this if PayCycle is also an array, how do I get the children as I did with Data.Items in the Linq above so that I can have the where clause filter on both these items?
You can deserialize the JSON into a dynamic object, and then use Linq to Objects:
[TestMethod]
public void TestMethod1()
{
const string json = #"""Items"": [
{
""Result"": {
""Id"": ""191e24b8-887d-e111-96ec-000c29128cee"",
""Name"": ""Name"",
""StartDate"": ""2012-04-03T00:00:00+01:00"",
""EndDate"": null,
""Status"": {
""Name"": ""Active"",
""Value"": 5
},
""Client"": {
""Id"": ""35ea10da-b8d5-4ef8-bf23-c829ae90fe60"",
""Name"": ""client Name"",
""AdditionalItems"": {}
},
""ServiceAgreement"": {
""Id"": ""65216699-a409-44b0-8294-0e995eb05d9d"",
""Name"": ""Name"",
""AdditionalItems"": {
""ScheduleBased"": true,
""PayFrequency"": {
""Id"": ""981acb72-8291-de11-98fa-005056c00008"",
""Name"": ""Weekly"",
""AdditionalItems"": {}
},
""PayCycle"": [
{
""Name"": ""Schedule Based"",
""ScheduleBased"": true,
""SelfBilling"": false,
""Id"": ""a8a2ecc4-ff79-46da-a135-743b57808ec3"",
""CreatedOn"": ""2011-09-16T23:32:19+01:00"",
""CreatedBy"": ""System Administrator"",
""ModifiedOn"": ""2011-09-16T23:32:19+01:00"",
""ModifiedBy"": ""System Administrator"",
""Archived"": false
}
]
}
}
}
}
]";
dynamic data = System.Web.Helpers.Json.Decode("{" + json + "}");
var result = from i in (IEnumerable<dynamic>)data.Items
where i.Result.ServiceAgreement.AdditionalItems.ScheduleBased == true
select new
{
i.Result.Client.Name,
i.Result.Client.Id
};
Assert.AreEqual(1, result.Count());
Assert.AreEqual("client Name", result.First().Name);
Assert.AreEqual("35ea10da-b8d5-4ef8-bf23-c829ae90fe60", result.First().Id);
}
Note that I had to add brackets { and } around your example json string, or else the .NET json parser doesn't like it.
To start off, I'm not familiar with writing LINQ/LAMBDA using this format ["some"]["thing"].
My first step would be to create some classes/objects to house the data in to ease creating any code afterwards.
e.g.
public class Result
{
public Guid Id { get; set; }
public string Name { get; set; },
public DateTime StartDate { get; set; }
//you get the idea
}
But possibly try the following?
var result = from p in data["Data"]["Items"].Children()
where (bool)p["Result"]["ServiceAgreement"]["AdditionalItems"]["ScheduleBased"] == true
&& (p["Result"]["ServiceAgreement"]["AdditionalItems"]["PayCycle"]).Where(o => o.["ScheduleBased"] == true)
select new
{
Name = (string)p["Result"]["Client"]["Name"],
Id = (string)p["Result"]["Client"]["Id"]
};

Categories

Resources