C# merge multiple lists based on timestamp - c#

I have a data set that comes as a list of objects in C#, looking something below.
public class MotorDataModel
{
public DateTime timestamp { set; get; }
public decimal MotorSpeed { set; get; }
public decimal MotorTemp { set; get; }
public decimal MotorKw{ set; get; }
}
public class MotorModel
{
public string MotorName { set; get; }
public List<MotorDataModel> MotorData { set; get; }
}
When I do the query, I will have 1 or more MotorModel records coming back (say motor 1, 2, 3, ...), each with their own timestamps, and various data points at those time stamps.
I am then sending this data to a javascript charting library, which takes the data in as a data table (e.g. spreadsheet like format), such as:
TimeStamp | Motor1:kW | Motor1:Speed | Motor1:Temp | Motor2:kW |Motor2:Speed ...
with the data following in rows. The data will be grouped on the timestamp, which should be the within a couple minutes of each other, in a consistent increment (say 15 minutes).
The plan is to transform the data in C#, convert it to JSON, and and send it to the chart library (Google Chart).
I don't have to format this in C#, and could convert the Object list data in C# to JSON, and reformat it in javascript on the client, but it seems better to transform it at the server.
Either way, I am struggling on how to transform the data from a multiple list of objects to a "datatable" like view.
This answer via LINQ seems to be close, but I have multiple lists of equipment, not a defined number.
I have also looked at just looping through and building the data table (or array), but unsure of what structure makes the most sense.
So, if anyone has done something similar, or has any feedback, it would be much appreciated.
Suggested format for providing sample data
Below is some sample data provided by BlueMonkMN. Please update the question providing sample data representative of your actual question.
List<MotorModel> allData = new List<MotorModel>() {
new MotorModel() {MotorName="Motor1", MotorData = new List<MotorDataModel> {
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 2, 56, 0), MotorSpeed=20.0M, MotorTemp=66.2M, MotorKw=5.5M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 10, 30), MotorSpeed=10.0M, MotorTemp=67.0M, MotorKw=5.5M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 25, 45), MotorSpeed=17.5M, MotorTemp=66.1M, MotorKw=5.8M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 40, 23), MotorSpeed=22.2M, MotorTemp=65.8M, MotorKw=5.4M}
}},
new MotorModel() {MotorName="Motor2", MotorData = new List<MotorDataModel> {
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 2, 58, 0), MotorSpeed=21.0M, MotorTemp=67.2M, MotorKw=5.6M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 11, 30), MotorSpeed=11.0M, MotorTemp=68.0M, MotorKw=5.6M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 24, 45), MotorSpeed=18.5M, MotorTemp=67.1M, MotorKw=5.9M},
new MotorDataModel(){timestamp=new DateTime(2016, 9, 18, 3, 39, 23), MotorSpeed=23.2M, MotorTemp=66.8M, MotorKw=5.5M}
}}
};

One possibility is to iterate through all the data, and build the table, as you suggest. I would suggest using a Dictionary, with the timestamp as the key. For each timestamp there can be multiple MotorData's, so it could have a list, like this:
Dictionary<DateTime, List<MotorDataModel>>
A code snippet to build this table would look like this:
List<MotorModel> motorModels; // filled in previously
// build result structure in this dictionary:
Dictionary<DateTime, List<MotorDataModel>> table = new Dictionary<DateTime, List<MotorDataModel>>();
// iterate through all motors and their data, and fill in the table
foreach(MotorModel m in motorModels)
{
foreach(MotorDataModel md in m.MotorData)
{
DateTime ts = md.timestamp;
// if this is the first occurance of the timestamp, create new 'row'
if (!table.ContainsKey(ts)) table[ts] = new List<MotorDataModel>();
// add the data to the 'row' of this timestamp
table[ts].Add(md);
}
}
// output the table
foreach(DateTime ts in table.Keys)
{
...
foreach(MotorDataModel md in table[ts])
{
...
}
}

I'd use Json.NET from NewtonSoft.
JObject o = new JObject();
foreach (MotorModel mm in allData) {
foreach (MotorDataModel mdm : mm.MotorData()) {
string key = mdm.TimeStamp.ToString(); // Or do your own format
o[key][mm.MotorName + ":kW"] = mdm.MotorKw;
o[key][mm.MotorName + ":Speed"] = mdm.MotorSpeed;
o[key][mm.MotorName + ":TEmp"] = mdm.MotorTemp;
}
}

Could you try something like this to compute your data:
var motorData = allData.SelectMany(x => x.MotorData).ToArray();
var starting = motorData.Min(x => x.timestamp);
var ending = motorData.Max(x => x.timestamp);
var duration = ending.Subtract(starting);
var blocks = (int)Math.Ceiling(duration.TotalMinutes / 15.0);
var query =
from b in Enumerable.Range(0, blocks)
let s = starting.AddMinutes(b * 15.0)
let e = starting.AddMinutes((b + 1.0) * 15.0)
select new
{
Timestamp = s,
MotorSpeedAverage =
motorData
.Where(x => x.timestamp >= s && x.timestamp < e)
.Average(x => x.MotorSpeed),
};
I get this result:

Related

Time series LINQ query

I have a central repository for IoT device logs. So as the logs arrive they have a timestamp. The problem I want to solve is, over a given time span, the same device might send multiple logs regarding its interaction with a specific catalyst. I want to consider that set of logs as a single event and not 5 disparate logs. I want to count the number of interactions. and not the number of logs.
Data Set
public class Data
{
public Guid DeviceId {get; set;}
public DateTime StartTime { get; set; }
public DateTime EndDateTime { get; set; }
public int Id { get; set; }
public int Direction { get; set;}
}
Data d1 = new Data();// imagine it's populated
Data d2 = new Data();// imagine it's populated
I am looking for a LINQ query that would yield something along the lines of
If ((d1.DeviceId == d2.DeviceId ) && (d1.Id == d2.Id) && (d1.Direction == d2.Direction) && (d1.StartTime - d2.StartTime < 15 minutes ))
If i know that the same IoT device is interacting with the same Id (catalyst) and the Direction is the same, and all of those logs occur within a 15 minute time span, It can be presumed that they correspond to the same catalyst event.
I do not control the log creation so ... no i cannot update the data to include "something" that would indicate the relationship.
Data per request... nothing fancy. I am sure most people suspect that I have 30+ properties and I only provide the one impacted by the calculation, but this is a simple set of possibilities
class SampleData
{
public List<Data> GetSampleData()
{
Guid device1 = Guid.NewGuid();
List<Data> dataList = new List<Data>();
Data data1 = new Data();
data1.DeviceId = device1;
data1.Id = 555;
data1.Direction = 1;
data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 0);
data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 30);
dataList.Add(data1);
//so this data point should be excluded in the final result
Data data2 = new Data();
data1.DeviceId = device1;
data1.Id = 555;
data1.Direction = 1;
data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 32);
data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 30);
dataList.Add(data2);
//Should be included because ID is different
Data data3 = new Data();
data1.DeviceId = device1;
data1.Id = 600;
data1.Direction = 1;
data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 2);
data1.EndDateTime = new DateTime(2010, 8, 18, 16, 32, 35);
dataList.Add(data3);
//exclude due to time
Data data4 = new Data();
data1.DeviceId = device1;
data1.Id = 600;
data1.Direction = 1;
data1.StartTime = new DateTime(2010, 8, 18, 16, 32, 37);
data1.EndDateTime = new DateTime(2010, 8, 18, 16, 33, 40);
dataList.Add(data4);
//include because time > 15 minutes
Data data5 = new Data();
data1.DeviceId = device1;
data1.Id = 600;
data1.Direction = 1;
data1.StartTime = new DateTime(2010, 8, 18, 16, 58, 42);
data1.EndDateTime = new DateTime(2010, 8, 18, 16, 58, 50);
dataList.Add(data5);
return dataList;
}
This turned out to be more complex than I hoped for.
I used a custom LINQ extension method I have called ScanPair which is a variation of my Scan method, which is an version of the APL scan operator (which is like Aggregate, but returns the intermediate results). ScanPair returns the intermediate results of the operation along with each original value. I think I need to think about how to make all of these more general purpose, as the pattern is used by a bunch of other extension methods I have for grouping by various conditions (e.g. sequential, runs, while test is true or false).
public static class IEnumerableExt {
public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, Func<T, TKey> seedFn, Func<(TKey Key, T Value), T, TKey> combineFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var seed = (seedFn(srce.Current), srce.Current);
while (srce.MoveNext()) {
yield return seed;
seed = (combineFn(seed, srce.Current), srce.Current);
}
yield return seed;
}
}
}
}
Now, you can use a tuple as an intermediate result to track the initial timestamp and the group number, and increment to the next (timestamp, group number) when the interval goes over 15 minutes. If you first group by the interaction, and then count the less than 15-minute groups per interaction, you get the answer:
var ans = interactionLogs.GroupBy(il => new { il.DeviceId, il.Id, il.Direction })
.Select(ilg => new {
ilg.Key,
Count = ilg.OrderBy(il => il.Timestamp)
.ScanPair(il => (firstTimestamp: il.Timestamp, groupNum: 1), (kvp, cur) => (cur.Timestamp - kvp.Key.firstTimestamp).TotalMinutes <= 15 ? kvp.Key : (cur.Timestamp, kvp.Key.groupNum + 1))
.GroupBy(ilkvp => ilkvp.Key.groupNum, ilkvp => ilkvp.Value)
.Count()
});
Here is a portion of a sample of intermediate results from ScanPair - the actual result is a ValueTuple with two fields, where the Key is the intermediate result (which is the ValueTuple of firstTimestamp,groupNum) and Value is the corresponding source (log) item. Using the function seeded version puts the first source item into the seed function to begin the process.
Key_firstTimestamp Key_groupNum Timestamp
7:58 PM 1 7:58 PM
7:58 PM 1 8:08 PM
7:58 PM 1 8:12 PM
8:15 PM 2 8:15 PM
8:15 PM 2 8:20 PM

Conditionally Include XElement in LINQ Select Operation?

I have working code in which I create a new var of type EnumerableRowCollection < XElement >. I have a new requirement where one of the XElements representing an Address must be conditionally included based on a Document Type value.
public class Taxes
{
public int DocumentType { get; set; }
private XElement BuildBodyXML()
{
// other stuff
Address billAddrObj = GetBillTo(dt);
Address buyerAddrObj = GetBuyerPrimary(dt);
var xBillTo = BuildAddress(billAddrObj, "BILL_TO");
var xBuyer = BuildAddress(buyerAddrObj, "BUYER_PRIMARY");
var INVOICE = from row in dt.AsEnumerable()
select new XElement(tcr + "INVOICE",
xBillTo, // This one needs to be conditionally included based on DocumentType
xBuyer,
// ... other elements ...
new XElement(tcr + "INVOICE_NUMBER", row.Field<string>("DOCNUMBR").Trim()));
// other stuff
return INVOICE;
}
public XElement BuildAddress(Address anAddress, string Name)
{
var xAddress = new XElement(tcr + Name);
// other stuff
return xAddress;
}
}
The Bill To XElement must be included conditionally based on the value of DocumentType. Can you help me achieve this?
UPDATE (Solution derived from answer by tinstaafl): I used the following code:
(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24 }.Contains(DocumentType) ? xBillTo : null),
You could use the ternary operator and set the conditional response that doesn't require the xBillTo object to null.

Insert Data into middle of range

I have the following model
public class DailyRoutine
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
}
Scenario:
When is created at initial time with 5 records which means 5 entries are entered for each day. Take an example May 1 to May 5 of 2017. Description have any string.
User can add a new record in the middle so that the following records should be moved and changed to next days.
Expected Output:
Example, user can give a date and description in input and submit. If the input date is '5/3/2017' (May 3), the entry should be added after May 2 record and the existing May 3 record changed to May 4, May 4 to May 5 etc. So the out is like May 1 to May 6 and the given input is updated on May 3.
Please help me to this with out degrading performance
This approach will work:
List<DailyRoutine> d = new List<DailyRoutine>()
{
new DailyRoutine() { Date = new DateTime(2017, 7, 1)},
new DailyRoutine() { Date = new DateTime(2017, 7, 2)},
new DailyRoutine() { Date = new DateTime(2017, 7, 3)},
new DailyRoutine() { Date = new DateTime(2017, 7, 4)},
new DailyRoutine() { Date = new DateTime(2017, 7, 5)}
};
DailyRoutine newDr = new DailyRoutine() { Date = new DateTime(2017, 7, 2) };
DailyRoutine oldDr = d.Where(dr => dr.Date == newDr.Date).FirstOrDefault();
if (oldDr != null)
{
int idx = d.IndexOf(oldDr);
List<DailyRoutine> changeList = d.Where((dr, i) => i >= idx).ToList();
foreach (DailyRoutine i in changeList)
{
i.Date = i.Date.AddDays(1);
}
d.Insert((int)idx, newDr);
}
else
{
d.Add(newDr);
}

Reorganize values in 3 arrays based off a unique combination of values in 2 of the arrays in C#

I have 3 arrays with the data types below:
string[] arrID = {"111", "222", "333", "444", "555", "666", "777"};
DateTime[] arrDates = new DateTime[]
{
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20)
};
string[] arrTime = {"8:20", "8:40", "8:20", "9:10", "8:20", "9:10", "8:20"};
I have added sample elements into the 3 arrays to simulate the types of data we will be having in these arrays.
Each index number in each of these elements contain values related to one record.
Example: The values in each of the array with index 3 contains values that make up one record.
I need to put values in these 3 arrays into the following 3 arrays: arrNEWID, arrNEWDate and arrNEWTime based on the following conditions:
For each unique date and time combination, a separate element needs to be added to each of the arrays.
If a unique date and time combination has more than one ID, then the IDs need to be separated by a comma.
Expected output in the 3 NEW arrays
--------------------------------------------------
| index | arrNEWDate | arrNEWTime | arrNEWID |
--------------------------------------------------
| 0 | 03/20/2015 | 8:20 | 111,333,777 |
| 1 | 03/20/2015 | 8:40 | 222 |
| 2 | 03/20/2015 | 9:10 | 666 |
| 3 | 03/21/2015 | 8:20 | 555 |
| 4 | 03/21/2015 | 9:10 | 444 |
Notes:
The data types on the 3 NEW arrays need to be string
The input and output need to remain as arrays. Lists may be created for processing
Here is the code I have used already:
string[] arrID = { "111", "222", "333", "444", "555", "666", "777" };
DateTime[] arrDates = new DateTime[]
{
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20)
};
string[] arrTime = { "8:20", "8:40", "8:20", "9:10", "8:20", "9:10", "8:20" };
string str_arrNEWID = "";
string str_arrNEWDate = "";
string str_arrNEWTime = "";
string[] arrNEWID = "".Split('~');
string[] arrNEWDate = "".Split('~');
string[] arrNEWTime = "".Split('~');
for (int i = 0; i <= arrID.GetUpperBound(0); i++)
{
int intExists = 0;
for (int j = 0; j <= arrNEWDate.GetUpperBound(0); j++)
{
//check if date matches for the current index being checked
if (arrNEWDate[j].ToString() == arrDates[i].Date.ToString("MM/dd/yyyy"))
{
//check if time matches for the same index
if (arrNEWTime[j].ToString() == arrTime[i].ToString())
{
//existing record
intExists = 1;
arrNEWID[j] = arrNEWID[j] + "," + arrID[i];
str_arrNEWID = string.Join("~", arrNEWID);
}
}
}
if (intExists == 0)
{
//new record
str_arrNEWDate = str_arrNEWDate + "~" + arrDates[i].Date.ToString("MM/dd/yyyy") ;
arrNEWDate = str_arrNEWDate.Split('~');
str_arrNEWTime = str_arrNEWTime + "~" + arrTime[i].ToString() ;
arrNEWTime = str_arrNEWTime.Split('~');
str_arrNEWID = str_arrNEWID + "~" + arrID[i];
arrNEWID = str_arrNEWID.Split('~');
}
}
The main challenge is that the compiler that needs to be used to compile this code doesn't support the lambda operator (=>)
I need to know:
If there is a way to do it so that I don't have to use the temporary string variables and split function to refresh the arrays each time
If there is any way to get rid of the multiple loops
arrays make everything more difficult.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
string[] arrID = { "111", "222", "333", "444", "555", "666", "777" };
DateTime[] arrDates = new DateTime[]
{
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20)
};
string[] arrTime = { "8:20", "8:40", "8:20", "9:10", "8:20", "9:10", "8:20" };
int i = -1;
int index = 0;
List<ewansObject> l2 = arrID.Select(o => new ewansObject() { date = arrDates[++i].ToString(), time = arrTime[i], id = arrID[i] }).GroupBy(k => k.date + k.time).Select(o => new ewansObject() { index = index++.ToString(), date = o.First().date, time = o.First().time, id = String.Join(",", o.Select(x => x.id)) }).ToList(); ;
string[] arrNEWID = l2.Select(x => x.id).ToArray();
string[] arrNEWDate = l2.Select(x => x.date).ToArray();
string[] arrNEWTime = l2.Select(x => x.time).ToArray();
Console.WriteLine(String.Join("|", arrNEWID));
Console.WriteLine(String.Join("|", arrNEWDate));
Console.WriteLine(String.Join("|", arrNEWTime));
}
public class ewansObject
{
public string index;
public string date;
public string time;
public string id;
}
}
}
maybe I should change my name to homework4points
The kind of processing you are looking for is very easily handled by the LINQ features in .NET. In particular, LINQ includes the very handy group...by and orderby which are directly applicable to your scenario. Unfortunately, the normal LINQ syntax also relies heavily on lambda syntax, which you say you cannot use.
However, note that the lambda syntax is just a "syntactic sugar", i.e. a way of expressing to the compiler something you could write yourself just in a more verbose way. In particular, the lambda syntax as used here generates anonymous methods, which you can of course just write as named methods.
Anonymous methods also allow "capturing" of variables, but in your scenario we can avoid needing to capture any variables by doing two things:
Put the data in class fields, where the named method can access them
Use the Enumerable.Select() overload that passes an index to the selector method, so that the named method can correlated the source items with the elements in the other two arrays that relate to each one
Likewise, while anonymous types would be handy here, effective use of those relies on anonymous methods and inferring types of the method parameters for those anonymous methods. In lieu of that, the Tuple class will serve just as well.
Putting all that together, we get something that looks like this:
class Program
{
static readonly string[] arrID = { "111", "222", "333", "444", "555", "666", "777" };
static readonly DateTime[] arrDates = new DateTime[]
{
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 21),
new DateTime(2015, 03, 20),
new DateTime(2015, 03, 20)
};
static readonly string[] arrTime = { "8:20", "8:40", "8:20", "9:10", "8:20", "9:10", "8:20" };
static void Main(string[] args)
{
// Generate intermediate query with grouped, ordered data
Tuple<DateTime, string>[] q = arrID.Select(CreateRecord)
.GroupBy(GetKey)
.OrderBy(GetGroupKey)
.Select(FlattenGroup)
.ToArray();
// Final output arrays here
string[] str_arrNEWDate = new string[q.Length],
str_arrNEWTime = new string[q.Length],
str_arrNEWID = new string[q.Length];
for (int i = 0; i < q.Length; i++)
{
str_arrNEWDate[i] = q[i].Item1.ToString("MM/dd/yyyy");
str_arrNEWTime[i] = q[i].Item1.ToString("HH:mm");
str_arrNEWID[i] = q[i].Item2;
// Diagnostic for demonstration purposes
Console.WriteLine("{0}: {1}", q[i].Item1, q[i].Item2);
}
}
private static Tuple<DateTime, string> CreateRecord(string id, int index)
{
return Tuple.Create(arrDates[index] + TimeSpan.Parse(arrTime[index]), id);
}
private static DateTime GetKey(Tuple<DateTime, string> record)
{
return record.Item1;
}
private static DateTime GetGroupKey(IGrouping<DateTime, Tuple<DateTime, string>> group)
{
return group.Key;
}
private static Tuple<DateTime, string> FlattenGroup(IGrouping<DateTime, Tuple<DateTime, string>> group)
{
return Tuple.Create(group.Key, string.Join(",", group.Select(GetId)));
}
private static string GetId(Tuple<DateTime, string> record)
{
return record.Item2;
}
}
As you can see, everywhere that you would normally write a lambda expression, in the above instead we just have a regular static method to do the work. Note that if you were dealing with a non-static class and the method needed to access instance members, the methods can simply be made non-static instead.
Delegate type inference takes care of constructing the delegate instances that need to be passed to the LINQ methods.
Note: while you don't say so explicitly in your question, the nature of the specifications strongly hints that this is homework. As such, you should make very sure you take the time to actually understand the above code example. Otherwise you have learned nothing, wasting your time in the class. Please feel free to ask questions here about my answer, and of course you should feel comfortable asking your teacher to help you understand both the original assignment and this answer.

How can I parse this using JSON.Net?

I'm trying to use JSON.Net to parse the results returned from a third party API.
As you can see the first block seems to be a description for the rows block columns that follow. I'm assuming this isn't standard practice as I cant find any reference to this style anywhere.
As it's not in the usual name:value pair format I'm a bit stumped.
{ cols: [{label: "name", type: 'string'},
{label: "caller_id_number", type: 'string'},
{label: "destination_number", type: 'string'},
{label: "call_start", type: 'datetime'},
{label: "duration", type: 'number'},
{label: "bill_seconds", type: 'number'},
{label: "uuid", type: 'string'},
{label: "call_bill_total", type: 'number'},
{label: "recorded", type: 'boolean'}],
rows: [
{c:[{v: "mydomain.com"},
{v: "1650"},
{v: "01902321654"},
{v: new Date(2011, 6, 19, 14, 12, 25)},
{v: 3},
{v: 0},
{v: "07752f6c-b203-11e0-92e6-495a2db86d6d"},
{v: 0},
{v: true}]}
,{c:[{v: "mydomain.com"},{v: "1652"},{v: "034534514"},{v: new Date(2011, 6, 19, 14, 11, 34)},{v: 53},{v: 27},{v: "e8fe3a06-b202-11e0-92dd-495a2db86d6d"},{v: 0.05},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1650"},{v: "034534580"},{v: new Date(2011, 6, 19, 14, 11, 34)},{v: 11},{v: 9},{v: "e8dfb9dc-b202-11e0-92dc-495a2db86d6d"},{v: 0.02},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1650"},{v: "03453453600"},{v: new Date(2011, 6, 19, 14, 11, 11)},{v: 14},{v: 9},{v: "db7efd52-b202-11e0-92d6-495a2db86d6d"},{v: 0.02},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1650"},{v: "0345345947"},{v: new Date(2011, 6, 19, 14, 9, 41)},{v: 42},{v: 21},{v: "a59314bc-b202-11e0-92c7-495a2db86d6d"},{v: 0.04},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1653"},{v: "345345420"},{v: new Date(2011, 6, 19, 14, 9, 41)},{v: 28},{v: 0},{v: "a5a953f8-b202-11e0-92c8-495a2db86d6d"},{v: 0},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1650"},{v: "353453120"},{v: new Date(2011, 6, 19, 14, 8, 52)},{v: 28},{v: 5},{v: "885515bc-b202-11e0-92bd-495a2db86d6d"},{v: 0.02},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1653"},{v: "34534567"},{v: new Date(2011, 6, 19, 14, 8, 36)},{v: 10},{v: 3},{v: "7efc86d0-b202-11e0-92b8-495a2db86d6d"},{v: 0.02},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1650"},{v: "34534584"},{v: new Date(2011, 6, 19, 14, 7, 43)},{v: 34},{v: 13},{v: "5f1cfb60-b202-11e0-92b2-495a2db86d6d"},{v: 0.02},{v: true}]},
{c:[{v: "mydomain.com"},{v: "1653"},{v: "34534534561"},{v: new Date(2011, 6, 19, 14, 6, 52)},{v: 52},{v: 0},{v: "411b3faa-b202-11e0-92ab-495a2db86d6d"},{v: 0},{v: true}]}]}
I've only got as far as
var o = JObject.Parse(results);
var records = o.SelectToken("rows").Select(s => s).ToList();
Ideally I'd like to pull the records back into a class such as
public class CallDetailRecord
{
public String Name { get; set; }
public String CallerIdNumber { get; set; }
public String DestinationNumber { get; set; }
public DateTime CallStart { get; set; }
public int Duration { get; set; }
public String Uuid { get; set; }
public Decimal CallBillTotal { get; set; }
public bool Recorded { get; set; }
}
Many thanks for any help.
I don't know what that is, but it's not JSON. It looks like javascript and would likely parse fine with a javascript engine.
JSON spec: http://json.org/
Validator: http://jsonlint.com/
While your sample data is not strictly valid JSON, your attempt to parse it was pretty close.
The layout that you're seeing is sometimes used by some parties who believe that the size of their result sets could be improved (decreased) by aliasing the field names. Unfortunately it isn't as straightforward to work with this, but you can pivot the items back into objects.
My preference in these cases is to use the dynamic keyword and ExpandoObjects. You can use a class if you like, as the bulk of the work of creating an object happens in the final Select() below and you can rewrite it to map the v element sets into fields of a class instead of an ExpandoObject. The syntax to access a field is the same, as you can see by the snippet at the end that writes all the values to the Console.
Note that I've written a helper lambda to handle the case of mapping Date() into DateTime(). I'm just pointing this out as you may have a better place to put this method (an extension method on DateTime, perhaps); but there's no harm in copying and pasting it as-is into a suitable place your code.
using System.Dynamic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
// ... other code removed
// You already have a means that loads your pseudo-json into results
// I used a file for the sake of this example
string results = File.ReadAllText(#"C:\temp\sample.json");
var o = JObject.Parse(results);
var headers = o.SelectToken("cols")
.Select(x => { return new { label = x.SelectToken("label").Value<string>(), type = x.SelectToken("type").Value<string>()}; }).ToArray();
var rows = o.SelectToken("rows").Select(s => { return s.SelectToken("c");}).ToList();
Func<JConstructor, DateTime> MapAsDateTime = (s) =>
{
// This is sloppy on my part, you should improve this as you like.
List<int> v = new List<int>();
foreach (JToken t in s)
{
v.Add(t.Value<int>());
}
return new DateTime(v[0], v[1], v[2], v[3], v[4], v[5]);
};
IEnumerable<dynamic> finalValues = rows.Select(s =>
{
var innerValues = s.ToList().Select(x => { return x.SelectToken("v"); }).ToArray();
int i = 0;
dynamic val = new ExpandoObject();
IDictionary<string, object> valueMap = (IDictionary<string, object>)val;
foreach (var innerValue in innerValues)
{
switch (headers[i].type)
{
case "string":
// NOTE: This can be improved, you could try to match and convert GUIDs with a regex or something else.
valueMap[headers[i].label] = innerValue.Value<string>();
break;
case "datetime":
valueMap[headers[i].label] = MapAsDateTime((JConstructor)innerValue);
break;
case "number":
// NOTE: This can be improved, your specific case needs decimal to handle things like 0.25, but many others could get by with just int
valueMap[headers[i].label] = innerValue.Value<decimal>();
break;
case "boolean":
valueMap[headers[i].label] = innerValue.Value<bool>();
break;
default:
// NOTE: You will need to add more cases if they 'define' more types.
throw new ArgumentException(string.Format("unhandled type \"{0}\" found in schema headers.", headers[i].type));
}
i++;
}
return val;
});
foreach (dynamic d in finalValues)
{
Console.WriteLine("name: {0}", d.name);
Console.WriteLine("caller_id_number: {0}", d.caller_id_number);
Console.WriteLine("destination_number: {0}", d.destination_number);
Console.WriteLine("call_start: {0}", d.call_start);
Console.WriteLine("duration: {0}", d.duration);
Console.WriteLine("bill_seconds: {0}", d.bill_seconds);
Console.WriteLine("uuid: {0}", d.uuid);
Console.WriteLine("call_bill_total: {0}", d.call_bill_total);
Console.WriteLine("recorded: {0}", d.recorded);
Console.WriteLine("--");
}
And finally, the sample output for the very first unit of data in your sample.
name: mydomain.com
caller_id_number: 1650
destination_number: 01902321654
call_start: 6/19/2011 2:12:25 PM
duration: 3
bill_seconds: 0
uuid: 07752f6c-b203-11e0-92e6-495a2db86d6d
call_bill_total: 0
recorded: True
--

Categories

Resources