I EDITED THE SOLUTION BELOW THE QUESTION !!!
I have read like 30 similar articles on this, but nothing quite hits the spot.
My scenario: in order to feed a charting library (highcharts/highstock) with data in ASP.NET I manually created the basic classes for such a graph. This is due to the fact that all the wrapper projects work for highcharts but NOT for highstock. However, highstock requires a HTML script block like this (working):
<script>
$('#container2').highcharts('StockChart',
{
"title": {
"text": "Graph_Title_string"
},
"yAxis": {
"title": {
"text": "YAxis_Title_string"
},
"height": 200,
"lineWidth": 2,
"top": 80
},
"series": [
{
"type": "line",
"name": "Testdata",
"data": [[Date.UTC(2014, 3, 8, 0, 0), 3], [Date.UTC(2014, 3, 9, 0, 0), 2], [Date.UTC(2014, 3, 10, 0, 0), 4], [Date.UTC(2014, 3, 11, 0, 0), 4], [Date.UTC(2014, 3, 12, 0, 0), 3], [Date.UTC(2014, 3, 13, 0, 0), 4], [Date.UTC(2014, 3, 14, 0, 0), 2], [Date.UTC(2014, 3, 15, 0, 0), 1], [Date.UTC(2014, 3, 16, 0, 0), 4], [Date.UTC(2014, 3, 17, 0, 0), 0]]
}]
});
I created the c# classes and their properties, as for example:
public class Series
{
public string type { get; set; }
public string name { get; set; }
public object data { get; set; }
}
Later on I use JsonConvert.SerializeObject to serialize my chart-object (with instanced classes title, yAxis, series etc within), which results in following output:
{
"title": {
"text": "Graph_Title_string"
},
"yAxis": {
"title": {
"text": "YAxis_Title_string"
},
"height": 200,
"lineWidth": 2,
"top": 0
},
"series": [
{
"type": "line",
"name": "Testdata",
"data": "[[Date.UTC(2014, 3, 8, 0, 0), 3],[Date.UTC(2014, 3, 9, 0, 0), 2],[Date.UTC(2014, 3, 10, 0, 0), 0],[Date.UTC(2014, 3, 11, 0, 0), 4],[Date.UTC(2014, 3, 12, 0, 0), 4],[Date.UTC(2014, 3, 13, 0, 0), 2],[Date.UTC(2014, 3, 14, 0, 0), 1],[Date.UTC(2014, 3, 15, 0, 0), 1],[Date.UTC(2014, 3, 16, 0, 0), 0],[Date.UTC(2014, 3, 17, 0, 0), 3]]"
}
]
}
So the problem is: the value of series->data is enclosed in quotes. As highstock obviously requires an object array as data ([[DateTime, value],[DateTime, value],...etc]), the chart is not rendered unless I remove these quotes around the array.
When the array was "pure" integer there would be no quotes (I guess), but as my points need to be DateTime/value I need an array of objects.
Therefore the question: how can I force my JSON serializer to NOT enclose my value of an object array in quotes?
Maybe this is trivial and simple, and I was looking to far. As said, I've read lots of articles on similar problems, but nothing worked for me. Any help highly appreciated!
SOLUTION:
the data-array in my series-node is int/double - DATE.UTC(2014, 3, 8, 0, 0) doesn't return a DateTime but a NUMBER (reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC)
Therefore defining a double array in my behind-class will result in the desired output format (as seen in the great suggestion below by craig and also the marked answer) without the quotation marks.
Code (reference: http://forums.asp.net/post/1463013.aspx ... however, slightly modified) for converting c# DateTime to the required milliseconds-timestamp:
public double MilliTimeStamp(DateTime d2)
{
DateTime d1 = new DateTime(1970, 1, 1);
TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);
return ts.TotalMilliseconds;
}
I created a class defined as follows:
public class Series
{
public string type { get; set; }
public string name { get; set; }
public List<object> data { get; set; }
}
Then used the following code to populate it and convert it to JSON.
Series series = new Series();
series.type = "foo";
series.name = "bar";
series.data = new List<object>();
for( int i = 0; i < 5; i++ )
{
series.data.Add( DateTime.Now );
series.data.Add( i );
}
var json = JsonConvert.SerializeObject( series );
The resulting JSON was:
{"type":"foo","name":"bar","data":["2014-04-17T07:47:16.3620755-07:00",0,"2014-0
4-17T07:47:16.3620755-07:00",1,"2014-04-17T07:47:16.3620755-07:00",2,"2014-04-17
T07:47:16.3620755-07:00",3,"2014-04-17T07:47:16.3620755-07:00",4]}
P.S. Declare data as object[] also generated the above JSON.
Answer from #CraigW. is very close. To make it closer to what you are after, try to declare data as collection of collection of object :
public class Series
{
public string type { get; set; }
public string name { get; set; }
public List<List<object>> data { get; set; }
}
Test code to populate the model :
Series series = new Series();
series.type = "foo";
series.name = "bar";
series.data = new List<List<object>>();
for (int i = 0; i < 5; i++)
{
var data = new List<object>();
data.Add(DateTime.Now);
data.Add(i);
series.data.Add(data);
}
var json = JsonConvert.SerializeObject(series, Formatting.Indented);
Test result :
{
"type": "foo",
"name": "bar",
"data": [
[
"2014-04-17T22:15:06.8812404+07:00",
0
],
[
"2014-04-17T22:15:06.8812404+07:00",
1
],
[
"2014-04-17T22:15:06.8812404+07:00",
2
],
[
"2014-04-17T22:15:06.8812404+07:00",
3
],
[
"2014-04-17T22:15:06.8812404+07:00",
4
]
]
}
Related
Making a text game to practice coding and ran across a really weird issue. I searched on this but most people are trying to clone one object to another whereas I am trying to point Object B to Object A because I want to change the properties of the same object.
public Armor Player_Chest_Armor { get; set; }
public Armor Player_Head_Armor { get; set; }
public Armor Player_Legs_Armor { get; set; }
public Weapon Player_Weapon { get; set; }
public Spell Player_Spell { get; set; }
public List<Consumable> Consumables { get; set; }
public List<IEquipment> Inventory { get; set; }
[JsonConstructor]
public Player (string name) {
this.Name = name;
this.Consumables = new List<Consumable>();
this.Inventory = new List<IEquipment>();
this.Inventory.Add(new Weapon("bronze sword", 22, 28, 25, 1.2, false));
this.Inventory.Add(new Armor("bronze chestplate", Armor.ArmorSlot.Chest, 35, 10, 20, false));
this.Inventory.Add(new Armor("bronze helmet", Armor.ArmorSlot.Head, 12, 3, 7, false));
this.Inventory.Add(new Armor("bronze legplates", Armor.ArmorSlot.Legs, 20, 5, 10, false));
this.Consumables.Add(new Consumable("minor health potion", 3, Consumable.PotionType.Health, 50));
this.Consumables.Add(new Consumable("minor mana potion", 3, Consumable.PotionType.Mana, 50));
this.Player_Spell = new Spell("Fireball", 50, 0, 1);
}
public void EquipInitialGear() {
this.EquipWeapon(this.Inventory[0] as Weapon);
this.EquipArmor(this.Inventory[1] as Armor);
this.EquipArmor(this.Inventory[2] as Armor);
this.EquipArmor(this.Inventory[3] as Armor);
}
public void DecreaseArmorDurability() {
try {
this.Player_Chest_Armor.DecreaseDurability();
}
This is the relevant part of code from my player class. Player_ whatever is for stuff that the player has equipped, whether it's armor, weapons, etc. I'm using an IEquipment interface to connect both armor and weapon objects in one list since a player inventory should include everything. When the player is created, the EquipWeapon and EquipArmor method calls are setting those inventory index objects to the appropriate player armor/weapon slot. Code for that below and assuming for simplicity that there is no weapon already equipped so it goes straight to the this.Player_Weapon = weapon line.
public void EquipWeapon(Weapon weapon) {
if (weapon.IsEquipped() == true) {
Console.WriteLine("You have already equipped {0}.", weapon.GetName());
return;
}
try {
if (this.Player_Weapon.Equipped == true) {
this.UnequipWeapon(this.Player_Weapon);
}
}
catch(NullReferenceException) {}
this.Player_Weapon = weapon;
weapon.Equipped = true;
Console.WriteLine("You have equipped {0}.", this.Player_Weapon.GetName());
}
When I try to change the property value of say Player_Weapon, it changes for that object, but the object in inventory (which should be the same) has the original property value. Decrease durability does the following in the Weapon class:
public void DecreaseDurability() {
this.Durability -= 1;
}
Every attack round, I have it call this method to decrease the weapon's durability by 1 out of a max of 100. In debugging mode, I can see where this.Player_Weapon.Durability goes from 100 to 99. However, if I go look at what should be the same object in inventory, it's durability is 100, which makes me think that it's creating a copy of the object instead of referencing the original object.
I'm convinced this is a pointer issue but I just can't see where the issue is. I want changes to the properties of Player_Weapon to also affect changes to the same object of that weapon in inventory that it should be pointing to.
Edited for clarity: Player, weapon and armor are all classes, not structs.
It's the JSON save game file. The reason this bug was so insidious that I asked SO for help is that it works fine, but when I quit the game which creates an autosave in JSON, it stores the object data. However, Player_Weapon and any of the Player_Armor are class pointers to the item in inventory. This works fine so long as the program is running. Once the JSON is created, and the data is saved, it appears that the Player class instances are decoupled from the inventory. This is what is saved in the file:
"Player_Chest_Armor": {
"Name": "bronze chestplate",
"ArmorCategory": 1,
"ItemValue": 35,
"ArmorRating": 12,
"Equipped": true,
"Durability": 98
},
"Player_Head_Armor": {
"Name": "bronze helmet",
"ArmorCategory": 0,
"ItemValue": 12,
"ArmorRating": 6,
"Equipped": true,
"Durability": 98
},
"Player_Legs_Armor": {
"Name": "bronze legplates",
"ArmorCategory": 2,
"ItemValue": 20,
"ArmorRating": 8,
"Equipped": true,
"Durability": 98
},
"Player_Weapon": {
"Name": "notched axe",
"RegDamage": 16,
"ItemValue": 10,
"CritMultiplier": 1.2,
"Equipped": true,
"Durability": 98
},
"Player_Spell": {
"Name": "Fireball",
"ManaCost": 50,
"Level": 1,
"SpellCategory": 0,
"FireOffense": {
"BlastDamage": 30,
"BurnDamage": 5,
"BurnCurRounds": 3,
"BurnMaxRounds": 3
}
},
"Consumables": [
{
"Name": "minor health potion",
"Quantity": 1,
"ItemValue": 3,
"Equipped": false,
"PotionCategory": 0,
"RestoreHealth": {
"RestoreHealthAmt": 50
}
},
{
"Name": "minor mana potion",
"Quantity": 1,
"ItemValue": 3,
"Equipped": false,
"PotionCategory": 1,
"RestoreMana": {
"RestoreManaAmt": 50
}
}
],
"Inventory": [
{
"$type": "DungeonGame.Weapon, DungeonGame",
"Name": "bronze sword",
"RegDamage": 27,
"ItemValue": 25,
"CritMultiplier": 1.2,
"Equipped": false,
"Durability": 98
},
{
"$type": "DungeonGame.Armor, DungeonGame",
"Name": "bronze chestplate",
"ArmorCategory": 1,
"ItemValue": 35,
"ArmorRating": 12,
"Equipped": true,
"Durability": 98
},
{
"$type": "DungeonGame.Armor, DungeonGame",
"Name": "bronze helmet",
"ArmorCategory": 0,
"ItemValue": 12,
"ArmorRating": 6,
"Equipped": true,
"Durability": 98
},
{
"$type": "DungeonGame.Armor, DungeonGame",
"Name": "bronze legplates",
"ArmorCategory": 2,
"ItemValue": 20,
"ArmorRating": 8,
"Equipped": true,
"Durability": 98
},
{
"$type": "DungeonGame.Weapon, DungeonGame",
"Name": "notched axe",
"RegDamage": 16,
"ItemValue": 10,
"CritMultiplier": 1.2,
"Equipped": true,
"Durability": 98
}
]
}
As indicated by the data in the save game file, there is no reference to the inventory item for the Player Weapon/Armor class data, so when the game reloads the data it decouples those objects from the original objects in inventory. Player Weapon/Armor is set to those values, but it is no longer referencing the instances of those classes in inventory, so it is creating a duplicate of the object.
The saved game code is creating a JSON file through serialization. From https://www.newtonsoft.com/json/help/html/PreserveObjectReferences.htm, "By default Json.NET will serialize all objects it encounters by value".
The solution to preserving the reference seems to be declaring and initializing the json using the following code:
string json = JsonConvert.SerializeObject(people, Formatting.Indented,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
I wasn't using PreserveReferencesHandling before, which was causing this bug.
I have a JSON data like bellow
[{
"CurrencyDenomination_JSON": "[{\"PK_MasterCurrencyDenomID\":1,\"CurrencyDenomination\":2000,\"NoofCurrency\":2,\"Total\":\"4000.00\",\"IsNote\":true},{\"PK_MasterCurrencyDenomID\":2,\"CurrencyDenomination\":500,\"NoofCurrency\":2,\"Total\":\"1000.00\",\"IsNote\":true}]"
}, {
"CurrencyDenomination_JSON": "[{\"PK_MasterCurrencyDenomID\":1,\"CurrencyDenomination\":2000,\"NoofCurrency\":5,\"Total\":\"10000.00\",\"IsNote\":true},{\"PK_MasterCurrencyDenomID\":2,\"CurrencyDenomination\":500,\"NoofCurrency\":2,\"Total\":\"1000.00\",\"IsNote\":true}]"
}, {
"CurrencyDenomination_JSON": "[{\"PK_MasterCurrencyDenomID\":1,\"CurrencyDenomination\":2000,\"NoofCurrency\":5,\"Total\":\"10000.00\",\"IsNote\":true},{\"PK_MasterCurrencyDenomID\":2,\"CurrencyDenomination\":500,\"NoofCurrency\":5,\"Total\":\"2500.00\",\"IsNote\":true}]"
}]
and I want to extract value from it and expect bellow data
[{
"PK_MasterCurrencyDenomID": 1,
"CurrencyDenomination": 2000,
"NoofCurrency": 2,
"Total": "4000.00",
"IsNote": true
}, {
"PK_MasterCurrencyDenomID": 2,
"CurrencyDenomination": 500,
"NoofCurrency": 2,
"Total": "1000.00",
"IsNote": true
}, {
"PK_MasterCurrencyDenomID": 3,
"CurrencyDenomination": 200,
"NoofCurrency": 2,
"Total": "400.00",
"IsNote": true
}, {
"PK_MasterCurrencyDenomID": 4,
"CurrencyDenomination": 100,
"NoofCurrency": 2,
"Total": "200.00",
"IsNote": true
}, {
"PK_MasterCurrencyDenomID": 5,
"CurrencyDenomination": 50,
"NoofCurrency": 2,
"Total": "100.00",
"IsNote": true
}, {
"PK_MasterCurrencyDenomID": 6,
"CurrencyDenomination": 20,
"NoofCurrency": 2,
"Total": "40.00",
"IsNote": true
}]
to do that I write bellow code ,and think this is not right way to do that there must be some smart way to do that .Please suggest me a better alternative.
JArray jsonArray = JArray.Parse(json);
List<MasterCurrencyDenomination> d = new List<MasterCurrencyDenomination>();
string strjson = string.Empty;
foreach (var item in jsonArray.Children())
{
strjson+= item["CurrencyDenomination_JSON"].ToString().Replace("[", "").Replace("]", ",");
}
d = JsonConvert.DeserializeObject<List<MasterCurrencyDenomination>>("["+strjson+"]");
If you observe the Json, it is evident that it is an Array of Type which contains a single String Property CurrencyDenomination_JSON. The Value of CurrencyDenomination_JSON is a JSON string.
What you need to do is, fetch these JSON strings (represented as IEnumerable<string>), retrieve JObject from them by parsing each of them and Serialize the collection.
var currencyArray = JArray.Parse(json).Children<JObject>()
.SelectMany(x=>x.Properties().Select(c=>c.Value.Value<string>()));
var result = JsonConvert.SerializeObject(currencyArray.SelectMany(x=>JArray.Parse(x)));
I think You can use the following code to do this.
List<MasterCurrencyDenomination> items = new List<MasterCurrencyDenomination>();
JToken.Parse(myJson).ToList().ForEach(x =>
{
items.AddRange(JsonConvert.DeserializeObject<List<MasterCurrencyDenomination>>(x.SelectToken("CurrencyDenomination_JSON").Value<string>()));
});
OR:
List<MasterCurrencyDenomination> items2 = new List<MasterCurrencyDenomination>();
foreach (var test in JToken.Parse(myJson))
{
items2.AddRange(JsonConvert.DeserializeObject<List<MasterCurrencyDenomination>>(test.SelectToken("CurrencyDenomination_JSON").Value<string>()));
}
It's a mostly conceptual question. I have some class representing algorithm removing text from a (custom) TextArea control. I'd like to test it - the algorithm of course ;). I'm concerned with the lack of readability of my test method:
[TestCase(new[] { "some text asdf" }, 5, 0, 9, 0, "some asdf", new int[0])]
[TestCase(new[] { "some text", "", "totally unimportant texttext that stays" }, 0, 0, 24, 2, "text that stays", new[] { 0, 1, 2 })]
public void ShouldRemoveSelectedText(string[] lines, int colStart, int lineStart, int colEnd, int lineEnd, string notRemovedText, int[] expectedRemovedLines) {
var newLines = algorithm.RemoveLines(lines, new TextPositionsPair {
StartPosition = new TextPosition(column: colStart, line: lineStart),
EndPosition = new TextPosition(column: colEnd, line: lineEnd)
});
Assert.That(newLines.LinesToChange.First().Value, Is.EqualTo(notRemovedText));
CollectionAssert.AreEqual(expectedRemovedLines, newLines.LinesToRemove.OrderBy(key => key));
}
As you can see it's a pretty simple test. I provide the algorithm with IEnumerable of string and selection area, however it's difficult to see - at first glance - which TestCase parameter goes where. I was wondering - is there a "cleaner" way of doing this?
Sidenote: I have tests that are as simple as this one, but must be provided with even more parameters...
A simple approach is to just use more lines per case...
[TestCase(new [] { "some text asdf" },
5, 0, 9, 0,
"some asdf",
new int[0])]
[TestCase(new [] { "some text", "", "totally unimportant texttext that stays" },
0, 0, 24, 2,
"text that stays",
new [] {0, 1, 2})]
public void ShouldRemoveSelectedText(...
Alternatively, you might use TestCaseSource, referring to a static array in your fixture class...
TestCaseData[] MySource = {
new TestCaseData(new [] { "some text asdf" },
5, 0, 9, 0,
"some asdf",
new int[0]),
new TestCaseData(new [] { "some text", "", "totally unimportant texttext that stays" },
0, 0, 24, 2
"text that stays",
new [] { 0, 1, 2})};
[TestCaseSource("MySource")]
public void ShouldRemoveSelectedText(..
Those are the best options I can see without changing the arguments to your test, which is what I would actually do if it were my code.
I'd create an object that encapsulates a text buffer and another for a selection. I'm showing classes here, but they might be structs...
class TextBuffer
{
public string[] Lines;
public Selection Selection;
...
}
class Selection
{
public int FromLine;
public int FromCol;
public int ToLine;
public int ToCol;
...
}
Then I'd rewrite the test in terms of those test primitives, making it all a lot easier to read.
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
--
I have need of a sort of specialized dictionary. My use case is this: The user wants to specify ranges of values (the range could be a single point as well) and assign a value to a particular range. We then want to perform a lookup using a single value as a key. If this single value occurs within one of the ranges then we will return the value associated to the range.
For example:
// represents the keyed value
struct Interval
{
public int Min;
public int Max;
}
// some code elsewhere in the program
var dictionary = new Dictionary<Interval, double>();
dictionary.Add(new Interval { Min = 0, Max = 10 }, 9.0);
var result = dictionary[1];
if (result == 9.0) JumpForJoy();
This is obviously just some code to illustrate what I'm looking for. Does anyone know of an algorithm to implement such a thing? If so could they point me towards it, please?
I have already tried implementing a custom IEqualityComparer object and overloading Equals() and GetHashCode() on Interval but to no avail so far. It may be that I'm doing something wrong though.
A dictionary is not the appropriate data structure for the operations you are describing.
If the intervals are required to never overlap then you can just build a sorted list of intervals and binary search it.
If the intervals can overlap then you have a more difficult problem to solve. To solve that problem efficiently you'll want to build an interval tree:
http://en.wikipedia.org/wiki/Interval_tree
This is a well-known data structure. See "Introduction To Algorithms" or any other decent undergraduate text on data structures.
This is only going to work when the intervals don't overlap. And your main problem seems to be converting from a single (key) value to an interval.
I would write a wrapper around SortedList. The SortedList.Keys.IndexOf() would find you an index that can be used to verify if the interval is valid and then use it.
This isn't exactly what you want but I think it may be the closest you can expect.
You can of course do better than this (Was I drinking earlier?). But you have to admit it is nice and simple.
var map = new Dictionary<Func<double, bool>, double>()
{
{ d => d >= 0.0 && d <= 10.0, 9.0 }
};
var key = map.Keys.Single(test => test(1.0))
var value = map[key];
I have solved a similar problem by ensuring that the collection is contiguous where the intervals never overlap and never have gaps between them. Each interval is defined as a lower boundary and any value lies in that interval if it is equal to or greater than that boundary and less than the lower boundary of the next interval. Anything below the lowest boundary is a special case bin.
This simplifies the problem somewhat. We also then optimized key searches by implementing a binary chop. I can't share the code, unfortunately.
I would make a little Interval class, which would something like that:
public class Interval
{
public int Start {get; set;}
public int End {get; set;}
public int Step {get; set;}
public double Value {get; set;}
public WriteToDictionary(Dictionary<int, double> dict)
{
for(int i = Start; i < End; i += Step)
{
dict.Add(i, Value);
}
}
}
So you still can a normal lookup within your dictionary. Maybe you should also perform some checks before calling Add() or implement some kind of rollback if any value is already within the dictionary.
You can find a Java flavored C# implementation of an interval tree in the Open Geospatial Library. It needs some minor tweaks to solve your problem and it could also really use some C#-ification.
It's Open Source but I don't know under what license.
i adapted some ideas for Dictionary and func, like "ChaosPandion" gave me the idea in his earlier post here above.
i still solved the coding, but if i try to refactor
i have a amazing problem/bug/lack of understanding:
Dictionary<Func<string, double, bool>, double> map = new Dictionary<Func<string, double, bool>, double>()
{
{ (a, b) => a == "2018" && b == 4, 815.72},
{ (a, b) => a == "2018" && b == 6, 715.72}
};
What is does is, that i call the map with a search like "2018"(year) and 4(month), which the result is double value 815,72.
When i check the unique map entries they look like this:
map working unique keys
so thats the orginal behaviour, anything fine so far.
Then i try to refactor it, to this:
Dictionary<Func<string, double, bool>, double> map =
new Dictionary<Func<string, double, bool>, double>();
WS22(map, values2018, "2018");
private void WS22(Dictionary<Func<string, double, bool>, double> map, double[] valuesByYear, string strYear)
{
int iMonth = 1;
// step by step this works:
map.Add((a, b) => (a == strYear) && (b == 1), dValue);
map.Add((a, b) => (a == strYear) && (b == 2), dValue);
// do it more elegant...
foreach (double dValue in valuesByYear)
{
//this does not work: exception after second iteration of foreach run
map.Add((a, b) => (a == strYear) && (b == iMonth), dValue );
iMonth+=1;
}
}
this works: (i use b==1 and b==2)
this does not work (map not working exception on add item on second iteration)
so i think the problem is, that the map does not have a unique key while adding to map dictionary. The thing is, i dont see my error, why b==1 is working and b==iMonth not.
Thx for any help, that open my eyes :)
Using Binary Search, I created an MSTest v2 test case that approaches the solution. It assumes that the index is the actual number you are looking for, which does not (might not?) suit the description given by the OP.
Note that the ranges do not overlap. And that the ranges are
[negative infinity, 0)
[0, 5]
(5, 15]
(15, 30]
(30, 100]
(100, 500]
(500, positive infinity]
This values passed as minimumValues are sorted, since they are constants in my domain. If these values can change, the minimumValues list should be sorted again.
Finally, there is a test that uses if statements to get to the same result (which is probably more flexible if you need something else than the index).
[TestClass]
public class RangeUnitTests
{
[DataTestMethod]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, -1, 0)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 0, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 1, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 5, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 7, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 15, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 16, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 30, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 31, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 100, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 101, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 500, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 501, 6)]
public void Use_BinarySearch_To_Determine_Range(int[] minimumValues, int inputValue, int expectedRange)
{
var list = minimumValues.ToList();
var index = list.BinarySearch(inputValue);
if (index < 0)
{
index = ~index;
}
Assert.AreEqual(expectedRange, index);
}
[DataTestMethod]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, -1, 0)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 0, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 1, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 5, 1)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 7, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 15, 2)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 16, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 30, 3)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 31, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 100, 4)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 101, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 500, 5)]
[DataRow(new[] { -1, 5, 15, 30, 100, 500 }, 501, 6)]
public void Use_Ifs_To_Determine_Range(int[] _, int inputValue, int expectedRange)
{
int actualRange = 6;
if (inputValue < 0)
{
actualRange = 0;
}
else if (inputValue <= 5)
{
actualRange = 1;
}
else if (inputValue <= 15)
{
actualRange = 2;
}
else if (inputValue <= 30)
{
actualRange = 3;
}
else if (inputValue <= 100)
{
actualRange = 4;
}
else if (inputValue <= 500)
{
actualRange = 5;
}
Assert.AreEqual(expectedRange, actualRange);
}
}
I did a little perfomance testing by duplicating the initial set [DataRow] several times (up to 260 testcases for each method). I did not see a significant difference in performance with these parameteres. Note that I ran each [DataTestMethod] in a seperate session. Hopefully this balances out any start-up costs that the test framework might add to first test that is executed.
You could check out the powercollections here found on codeplex that has a collection that can do what you are looking for.
Hope this helps,
Best regards,
Tom.