Dynamically set properties of a Dynamic object - c#

I have a dynamic object which basically holds an AvroRecord. AvroRecord class details here.
I can assign values to the properties statically but I was wondering if this could be done dynamically. I have looked at the forum question here ,here and also here. But none of these work for me.
This is the static code that works.
var serializer = AvroSerializer.CreateGeneric(Schema);
var rootSchema = serializer.WriterSchema as RecordSchema;
dynamic counterpartRow = new AvroRecord(rootSchema);
counterpartRow.CounterpartID = Row.CounterpartID
counterpartRow.CounterpartFirstDepositDate = Row.CounterpartFirstDepositDate
The Row is an object of the InputBuffer class of SSIS and it holds all the columns coming from the upstream data source.
The schema variable used above is an avro schema, which is something like this.
Schema = #"{
""type"":""record"",
""name"":""Microsoft.Hadoop.Avro.Specifications.Counterparts"",
""fields"":
[
{ ""name"":""CounterpartID"", ""type"":""int"" },
{ ""name"":""CounterpartFirstDepositDate"", ""type"":[""string"",""null""] },
{ ""name"":""CounterpartFirstTradeDate"",""type"":[""string"",""null""] },
{ ""name"":""ClientSegmentReportingID"",""type"":""int"" },
{ ""name"":""ClientSegmentReportingName"", ""type"":[""string"",""null""] },
{ ""name"":""ContractID"", ""type"":""int""},
{ ""name"":""ContractFirstDepositDate"", ""type"":[""string"",""null""]},
{ ""name"":""ContractFirstTradeDate"",""type"":[""string"",""null""] },
{ ""name"":""ContractClosingOffice"",""type"":[""string"",""null""] },
{ ""name"":""LeadCreationDate"", ""type"":[""string"",""null""] },
{ ""name"":""ContractCountryOfResidence"", ""type"":[""string"",""null""]}
]
}";
I have tried something like the earlier forum links suggested like
counterpartRow.GetType().GetField("CounterpartID").SetValue(Row, Row.CounterpartID, null);
and also the other method (which apparently should work for for dynamic type), but even that does not.
foreach (string propertyName in GetPropertyKeysForDynamic(counterpartRow.Schema.Fields()))
{
string propertyValue = counterpartRow[propertyName];
}
and the function defined like this.
public List<string> GetPropertyKeysForDynamic(dynamic dynamicToGetPropertiesFor)
{
var jObject = (JObject)JToken.FromObject(dynamicToGetPropertiesFor);
Dictionary<string, object> values = jObject.ToObject<Dictionary<string, object>>();
List<string> toReturn = new List<string>();
foreach (string key in values.Keys)
{
toReturn.Add(key);
}
return toReturn;
}
The dictionary above returns blank.
the Row mentioned above is an object of InputBuffer class (auto generated class in SSIS).
which is something like this.
public class Input0Buffer: ScriptBuffer
{
public Input0Buffer(PipelineBuffer Buffer, int[] BufferColumnIndexes, OutputNameMap OutputMap)
: base(Buffer, BufferColumnIndexes, OutputMap)
{
}
public Int32 CounterpartID
{
get
{
return Buffer.GetInt32(BufferColumnIndexes[0]);
}
}
------more properties
If you see my original static code, I am trying to dynamically generate the assignment. I have already dynamically generated the schema (instead of the static definition I have given above). So, the only piece left to generate dynamically is the assignment of the assignment. An idea could be that I generate the string but how do I then execute that string? That is only if there is no way to achieve this.

If I understand correct your question I think you can try something like this
string _schema = "your avro schema"
RecordSchema _record = (RecordSchema)Avro.Schema.Parse(_schema);
GenericRecord _generic_record = new GenericRecord(payload_record);
for (int ii = 0; ii < _record.Fields.Count; ii++)
{
_generic_record.Add(_record.Fields[ii].Name, Raw.TheFiledYouNeed);
}

Related

How to easily merge two anonymous objects with different data structure?

I would like to merge these two anonymous objects:
var man1 = new {
name = new {
first = "viet"
},
age = 20
};
var man2 = new {
name = new {
last = "vo"
},
address = "123 street"
};
Into a single one:
var man = new {
name = new {
first = "viet",
last = "vo"
},
age = 20,
address = "123 street"
};
I looked for a solution but found nothing clever.
Convert the anonymous object to ExpandoObject which is essentially a dictionary of string key and object value:
var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();
public static ExpandoObject ToDynamic(this object obj)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in obj.GetType().GetProperties())
{
var currentValue = propertyInfo.GetValue(obj);
if (propertyInfo.PropertyType.IsAnonymous())
{
expando.Add(propertyInfo.Name, currentValue.ToDynamic());
}
else
{
expando.Add(propertyInfo.Name, currentValue);
}
}
return expando as ExpandoObject;
}
I'm using a helper extension to establish whether a type is an anonymous one:
public static bool IsAnonymous(this Type type)
{
return type.DeclaringType is null
&& type.IsGenericType
&& type.IsSealed
&& type.IsClass
&& type.Name.Contains("Anonymous");
}
Then, merge two resulting expando objects into one, but recursively, checking for nested expando objects:
var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);
public static IDictionary<string, object> MergeDictionaries(
IDictionary<string, object> targetDictionary,
IDictionary<string, object> sourceDictionary,
bool overwriteTarget)
{
foreach (var pair in sourceDictionary)
{
if (!targetDictionary.ContainsKey(pair.Key))
{
targetDictionary.Add(pair.Key, sourceDictionary[pair.Key]);
}
else
{
if (targetDictionary[pair.Key] is IDictionary<string, object> innerTargetDictionary)
{
if (pair.Value is IDictionary<string, object> innerSourceDictionary)
{
targetDictionary[pair.Key] = MergeDictionaries(
innerTargetDictionary,
innerSourceDictionary,
overwriteTarget);
}
else
{
// What to do when target propety is nested, but source is not?
// Who takes precedence? Target nested property or source value?
if (overwriteTarget)
{
// Replace target dictionary with source value.
targetDictionary[pair.Key] = pair.Value;
}
}
}
else
{
if (pair.Value is IDictionary<string, object> innerSourceDictionary)
{
// What to do when target propety is not nested, but source is?
// Who takes precedence? Target value or source nested value?
if (overwriteTarget)
{
// Replace target value with source dictionary.
targetDictionary[pair.Key] = innerSourceDictionary;
}
}
else
{
// Both target and source are not nested.
// Who takes precedence? Target value or source value?
if (overwriteTarget)
{
// Replace target value with source value.
targetDictionary[pair.Key] = pair.Value;
}
}
}
}
}
return targetDictionary;
}
The overwriteTarget parameter decides which object takes priority when merging.
Usage code:
var man1 = new
{
name = new
{
first = "viet",
},
age = 20,
};
var man2 = new
{
name = new
{
last = "vo",
},
address = "123 street",
};
var man1Expando = man1.ToDynamic();
var man2Expando = man2.ToDynamic();
dynamic result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);
Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));
and the result:
{
"name": {
"first": "viet",
"last": "vo"
},
"age": 20,
"address": "123 street"
}
Notice how I assigned the result to dynamic. Leaving compiler assign the type will leave you with expando object presented as IDictionary<string, object>. With a dictionary representation, you cannot access properties in the same manner as if it was an anonymous object:
var result = MergeDictionaries(man1Expando, man2Expando, overwriteTarget: true);
result.name; // ERROR
That's why the dynamic. With dynamic you are losing compile time checking, but have two anonymous objects merged into one. You have to judge for yourself if it suits you.
There's nothing built-in in the C# language to support your use case. Thus, the question in your title needs to be answered with "Sorry, there is no easy way".
I can offer the following alternatives:
Do it manually:
var man = new {
name = new {
first = man1.name.first,
last = man2.name.first
},
age = man1.age,
address = man2.address
};
Use a class instead of an anonymous type for the resulting type (let's call it CompleteMan). Then, you can
create a new instance var man = new CompleteMan(); ,
use reflection to collect the properties and values from your "partial men" (man1 and man2),
assign those values to the properties of your man.
It's "easy" in the sense that the implementation will be fairly straight-forward, but it will still be a lot of code, and you need to take your nested types (name) into account.
If you desperately want to avoid non-anonymous types, you could probably use an empty anonymous target object, but creating this object (var man = new { name = new { first = (string)null, last = (string)null, ...) is not really less work than creating a class in the first place.
Use a dedicated dynamic data structure instead of anonymous C# classes:
The Newtonsoft JSON library supports merging of JSON objects.
Dictionaries can also be merged easily.
ExpandoObjects can be merged easily as well.

JSON Data to dynamic (Anonymous type) conversion

I'm having trouble converting JSON data to a dynamic type definition. I've looked at JObject, JsonConvert serialize/deserialize and nothing works. The closest thing to making this work is JsonConvert.DeserializeAnonymousType but this requires a definition that matches the JSON. In my case, the JSON is quite complicated so I really need to convert an instance of the JSON to a fully anonymous, dynamic type.
dynamic rawjson = #"{ 'tags': { 'abcd' : '12345' },'properties': { 'desired': { 'PropOne' : '2345', 'PropTwo' : '6789' } } }";
#region reference object
dynamic reference = new
{
SomeName = $"xxxx",
initialTwin = new
{
tags = new { abcd = 12345 },
properties = new
{
desired = new
{
PropOne = "2345",
PropTwo = "6789"
}
}
}
};
#endregion
dynamic anonobject = JsonConvert.DeserializeAnonymousType(rawjson, reference.initialTwin);
dynamic testobject = new
{
SomeName = $"xxxx",
initialTwin = $"{anonobject}"
};
I need the "testobject" in the code above to look exactly like the "reference" object. Using the DesializeAnonymousType gets me very close but the definition would be very hard to create and maintain.
How can I get the same results working from an instance of JSON data without typing the definition for DeserializeAnonyousType? Is that possible?
I have tested the way below and it worked for me:
var anonobject = JsonConvert.DeserializeObject<ExpandoObject>(rawjson);
dynamic testobject = new
{
SomeName = $"xxxx",
initialTwin = anonobject
};
But since this is a dynamic object, you need to know the properties you may want to use.
Example:
Console.WriteLine(testobject.initialTwin.tags.abcd);

c# dynamic object workaround

I have a block of code that works in .NET 4.0+, but I need to use this code in an SSIS package that only supports up to .NET 3.5. The problem is I can't use the dynamic object below 4.0. I'm unable to find a workaround, any ideas?
string json = File.ReadAllText(#"C:json.txt");
dynamic deserialisedJson = JsonConvert.DeserializeObject(json);
var locations = new List<Location>();
foreach (var root in deserialisedJson)
{
foreach (var state in root)
{
foreach (var city in state)
{
foreach (var location in city)
{
Location loc = new Location();
loc.CafeId = location.First["cafeID"];
loc.CafeName = location.First["cafeName"];
loc.CafeState = location.First["cafeState"];
loc.CafeCity = location.First["cafeCity"];
loc.CafeStreetName = location.First["cafeStreetName"];
loc.CafeZip = location.First["cafeZip"];
locations.Add(loc);
}
}
}
}
UPDATE
Adding JSON schema
{
"AK": {
"Anchorage": [{
"Name": "John Doe",
"Address": "123 Main St.",
"City": "Anchorage",
"State": "AK",
"Zip": "12345"
}],
"Fairbanks": [{
"Name": "Sally Smith",
"Address": "987 Main St.",
"City": "Fairbanks",
"State": "AK",
"Zip": "98765"
}]
}
}
UPDATE 2
I am attempting the IEnumerable workaround, but not sure what the correct syntax is so that I can grab the values I need:
string json = File.ReadAllText(#"C:json.txt");
var deserialisedJson = (IEnumerable)JsonConvert.DeserializeObject(json);
var locations = new List<Location>();
foreach (var root in deserialisedJson)
{
foreach (var state in (IEnumerable)root)
{
foreach (var city in (IEnumerable)state)
{
foreach (var location in (IEnumerable)city)
{
Location loc = new Location();
loc.Name = //What goes here???
loc.Address = //What goes here???
loc.City = //What goes here???
loc.State = //What goes here???
loc.Zip = //What goes here???
locations.Add(loc);
}
}
}
}
from another post - Newtonsoft JSON Deserialize
class MyData
{
public string t;
public bool a;
public object[] data;
public string[][] type;
}
and then use the generic version of DeserializeObject:
MyData tmp = JsonConvert.DeserializeObject<MyData>(json);
foreach (string typeStr in tmp.type[0])
{
// Do something with typeStr
}
Without an example json I can only speculate - but it looks like you know the (relevant) schema of your json already. You don't need dynamic in the first place, and even in .net 4.0 and higher I'd advise against using it. Code using dynamic is typically slower, more error prone, and harder to debug than code which is statically typed not just because of compile-time checks, but also because errors at runtime appear earlier.
From your limited example and without knowing what that First thing is, it looks to me like you could do something like...
class LocationFromJson {
public LocationContentsFromJson First;
}
class LocationContentsFromJson {
public string cafeID, cafeName, cafeState, cafeCity, cafeStreetName, cafeZip;
}
//Later usage; this should be equivalent to your example:
var deserialisedJson = JsonConvert.DeserializeObject<LocationFromJson[][][][]>(json);
var locations =
deserialisedJson //4 levels of enumerable
.SelectMany(o => o) //3 levels of enumerable
.SelectMany(o => o) //2 levels of enumerable
.SelectMany(o => o) //1 level of enumerable
.Select(o => new Location {
CafeId = o.First.cafeID,
CafeName = o.First.cafeName,
CafeState = o.First.cafeState,
CafeCity = o.First.cafeCity,
CafeStreetName = o.First.cafeStreetName,
CafeZip = o.First.cafeZip,
}).ToArray();
To be explicit: this may or may not work unaltered. Your example does not include the type declaration of Location nor an example json, so I'm speculating here a little: Location.CafeId might also be an int; I can't tell from your question.
But this shouldn't be too far from what you need.

C# Trying to mask child values in a dynamic object

At the moment I'm adding functionality to our service that will take in an object that is about to be logged to trace and mask any sensitive fields that are included in the object.
The issue is that we can get objects with different layers. The code I have written so far only handles a parent field and a single child field and uses a nasty embedded for loop implementation to do it.
In the event that we have a third embedded layer of fields in an object we want to log, this wouldn't be able to handle it at all. There has to be a more efficient way of handling generic parsing of a dynamic object, but so far it's managed to avoid me.
The actual code that deserializes and then masks field sin the object looks like this:
private string MaskSensitiveData(string message)
{
var maskedMessage = JsonConvert.DeserializeObject<dynamic>(message);
LoggingProperties.GetSensitiveFields();
for (int i = 0; i < LoggingProperties.Fields.Count(); i++)
{
for (int j = 0; j < LoggingProperties.SubFields.Count(); j++)
{
if (maskedMessage[LoggingProperties.Fields[i]] != null)
{
if (maskedMessage[LoggingProperties.Fields[i]][LoggingProperties.SubFields[j]] != null)
{
maskedMessage[LoggingProperties.Fields[i]][LoggingProperties.SubFields[j]] = MaskField(LoggingProperties.SubFieldLengths[j]);
}
}
}
}
return maskedMessage.ToString(Formatting.None);
}
And it works off of a LoggingProperties class that looks like this:
public static class LoggingProperties
{
// Constants indicating the number of fields we need to mask at present
private const int ParentFieldCount = 2;
private const int SubFieldCount = 4;
// Constant representing the character we are using for masking
public const char MaskCharacter = '*';
// Parent fields array
public static string[] Fields = new string[ParentFieldCount];
// Subfields array
public static string[] SubFields = new string[SubFieldCount];
// Array of field lengths, each index matching the subfield array elements
public static int[] SubFieldLengths = new int[SubFieldCount];
public static void GetSensitiveFields()
{
// Sensitive parent fields
Fields[0] = "Parent1";
Fields[1] = "Parent2";
// Sensitive subfields
SubFields[0] = "Child1";
SubFields[1] = "Child2";
SubFields[2] = "Child3";
SubFields[3] = "Child4";
// Lengths of sensitive subfields
SubFieldLengths[0] = 16;
SubFieldLengths[1] = 16;
SubFieldLengths[2] = 20;
SubFieldLengths[3] = 3;
}
}
}
The aim was to have a specific list of fields for the masking method to look out for that could be expanded or contracted along with our systems needs.
The nested loop method though just seems a bit roundabout to me. Any help is appreciated.
Thanks!
UPDATE:
Here's a small example of a parent and child record that would be in the message prior to the deserialize call. For this example say I'm attempting to mask the currency ID (So in properties the fields could be set like this: Parent1 = "Amounts" and Child1 = "CurrencyId"):
{
"Amounts":
{
"Amount":20.0,
"CurrencyId":826
}
}
An example of a problem would then be if the Amount was divided into pounds and pence:
{
"Amounts":
{
"Amount":
{
"Pounds":20,
"Pence":0
},
"CurrencyId":826
}
}
This would another layer and yet another embedded for loop...but with that I would be making it overly complex and difficult if the next record in a message had only two layers.
Hope this clarifies a few things =]
Okay, I've really tried but I couldn't figure out an elegant way. Here's what I did:
The first try was using reflection but since all the objects are of type JObject / JToken, I found no way of deciding whether a property is an object or a value.
The second try was (and still is, if you can figure out a good way) more promising: parsing the JSON string into a JObject with var data = JObject.Parse(message) and enumerating its properties in a recursive method like this:
void Mask(data)
{
foreach (JToken token in data)
{
if (token.Type == JTokenType.Object)
{
// It's an object, mask its children
Mask(token.Children());
}
else
{
// Somehow mask it but I couldn't figure out to do it with JToken
// Pseudocode, it doesn't actually work:
if (keysToMask.Contains(token.Name))
token.Value = "***";
}
}
}
Since it doesn't work with JTokens, I've tried the same with JProperties and it works for the root object, but there's a problem: although you can see if a given JProperty is an object, you can not select its children object, JProperty.Children() gives JToken again and I found no way to convert it to a JProperty. If anyone knows how to achieve it, please post it.
So the only way I found is a very dirty one: using regular expressions. It's all but elegant - but it works.
// Make sure the JSON is well formatted
string formattedJson = JObject.Parse(message).ToString();
// Define the keys of the values to be masked
string[] maskedKeys = {"mask1", "mask2"};
// Loop through each key
foreach (var key in maskedKeys)
{
string original_pattern = string.Format("(\"{0}\": )(\"?[^,\\r\\n]+\"?)", key);
string masked_pattern = "$1\"censored\"";
Regex pattern = new Regex(original_pattern);
formatted_json = pattern.Replace(formatted_json, masked_pattern);
}
// Parse the masked string
var maskedMessage = JsonConvert.DeserializeObject<dynamic>(formatted_json);
Assuming this is your input:
{
"val1" : "value1",
"val2" : "value2",
"mask1" : "to be masked",
"prop1" : {
"val3" : "value3",
"val1" : "value1",
"mask2" : "to be masked too",
"prop2" : {
"val1" : "value 1 again",
"mask1" : "this will also get masked"
}
}
}
This is what you get:
{
"val1": "value1",
"val2": "value2",
"mask1": "censored",
"prop1": {
"val3": "value3",
"val1": "value1",
"mask2": "censored",
"prop2": {
"val1": "value 1 again",
"mask1": "censored"
}
}
}

XML import to C# Object

I have an XML file with the follow in it:
<tooltip>
<text>My Tool Tip</text>
<color>#000000</color>
<crosshairs>
<width>3</width>
<color>green</color>
<padding>5px</padding>
</crosshairs>
<crosshairs>
<width>3</width>
<color>blue</color>
</crosshairs>
</tooltip>
I wish to load the "crosshairs" elements into a C# object - the object is then serialized out to a JSON file:
ToolTipClass toolTip = new ToolTipClass(xmlDoc);
JavaScriptSerializer s = new JavaScriptSerializer();
string aJSON = s.Serialize(toolTip);
// aJSON is now written out to file.
I do have a "toolTip" class with some public variables which are set by default in the constructor. The constuctor also reads the XML file in and overwrites the public variables with the value in the XML (e.g. is written into "public string text;", but the "crosshairs" part of the tool tip can contain either 1 or several elements - I don't want to define a class for the crosshairs as the tags within the crosshairs can be more than just the 2 defined above (width, color). eg (padding, margin, fontSize etc.)
the JSon output would look something like this:
tooltip: {
text: "My Tool Tip",
color: "#000000",
crosshairs: [{
width: 3,
color: 'green',
padding: '5px'
}, {
width: 3,
color: 'blue'
}]
}
What I need to know is How do I load the crosshairs elements into an object that isn't predefiend?
I have looked at: http://danielwylie.me/blog/2010/04/26/c-convert-xml-to-an-object-or-list-of-an-object/?Focus=Yes
but this uses a "person" class which is not what I wanted.
Many thanks for any help/pointers.
Extra:
ToolTip Class:
public class ToolTip
{
public string backgroundColor = "rgba(255, 255, 255, .80)";
public string borderColor = "#764D9B";
public int borderRadius = 5;
public int borderWidth = 1;
//public string crosshairs = null;
//public List<object> crosshairs = new List<object>();
public Boolean enabled = true;
//formatter: ;
public Boolean shadow = true;
public Boolean shared = false;
public float snap = 10;
public HCCSS style = new HCCSS();
public string text = "[undefined]";
public string color = "#000000";
public HCToolTip(XmlDocument xmlDoc) {
text = findTagInXML_String(xmlDoc, "//tooltip/text", text);
color = findTagInXML_String(xmlDoc, "//tooltip/color", color);
//snip
}
static private string findTagInXML_String(XmlDocument xmlDoc, string tag, string defaultvalue) {
return xmlDoc.SelectSingleNode(tag) == null || xmlDoc.SelectSingleNode(tag).InnerText == "null" ? defaultvalue : xmlDoc.SelectSingleNode(tag).InnerText;
}
}
update / reply ## (not sure how to do replies below).
Thanks for the code and the link to the converter site. I've added in some code and sort of got it doing something, but I do have a few more problems.
How do I get the XML data into the Crosshairs collection. I've currently got this in my toolTip constructor class:
Crosshairs c = new Crosshairs();
c.SetProperty("a",new {width="3", color="green"});
c.SetProperty("b", new { width = "3", color = "blue" });
crosshairs.Add(c);
I'm assuming where I've got the new width color is where I would want it to bring in the details from the XML file mentioned above.
I've added in the Converter class but the output I'm now getting is something like:
tooltip: {
borderColor: "#F00"
crosshairs: {
List: [
{
Value: {
width: "3"
color: "green"
}
Text: {
width: "3"
color: "blue"
}
}
]
}
enabled: true
}
I did change the example converter to have the following rows:
foreach (Crosshairs item in listType) {
//Add each entry to the dictionary.
Dictionary<string, object> listDict = new Dictionary<string, object>();
listDict.Add("Value", item.GetProperty("a"));
listDict.Add("Text", item.GetProperty("b"));
itemsList.Add(listDict);
}
result["List"] = itemsList;
As you can see it' doesn't look very generic as it uses types of "CrosshairsCollection", but I'm sort of guessing that I can change the "CrosshairsCollection" to a "GenericCollection" as it's only a dictionary - so #1 above still applies.
The other problem is that not only the toolTip has this "crosshairs" class, but I do have other classes that are similar to crosshairs - eg style which I would like to have the same system.
If anyone could help with #1 above - importing the data from XML - to generic class rather than a predefined class it would really help.
Again - Many thanks for the help.
Alan
Define a simple Crosshairs type that is a wrapper for a string, object dictionary:
class CrosshairsCollection : List<Crosshairs>
{
}
class Crosshairs
{
private Dictionary<string, object> dict = new Dictionary<string,object>();
public IEnumerable<KeyValuePair<string, object>> GetAllProperties()
{
foreach (string key in dict.Keys)
{
yield return new KeyValuePair<string, object>(key, dict[key]);
}
}
public object GetProperty(string s)
{
object value;
bool exists = dict.TryGetValue(s, out value);
if (!exists)
{
value = null;
}
return value;
}
public void SetProperty(string s, object o)
{
if (!dict.ContainsKey(s))
{
dict.Add(s, o);
}
else
{
dict[s] = o;
}
}
}
Then implement a JavaScriptConverter for CrosshairsCollection, similar to this: http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptconverter.aspx
Replace your List with CrosshairsCollection and register the JavaScriptConverter with your JavaScriptSerializer instance.
[Edit: answers to follow up questions]
I'm assuming that you have two follow-up questions, both numbered 1, and that the second one is "how do I get the JSON output to be more like my original example?" :)
The way I interpreted your original XML was that a tooltip has multiple crosshairs, and not a single crosshairs with multiple, overlapping property definitions. The representation in the XML and your name usage in the code aren't in accord though: you call the entire thing "crosshairs." Bear that in mind as you're reading my original and edited response.
So to get an object that mimics the original XML, I would have expected you to provide the properties like so:
CrosshairsCollection crosshairsList = new CrosshairsCollection();
Crosshairs c1 = new Crosshairs();
c1.SetProperty("width", 3);
c1.SetProperty("color", "green");
c1.SetProperty("padding", "5px");
crosshairsList.Add(c1);
Crosshairs c2 = new Crosshairs();
c2.SetProperty("width", 3);
c2.SetProperty("color", "blue");
crosshairsList.Add(c2);
Take that one step further, turn each Crosshairs initialization into a factory method, and plug it into your XML representation, like you're doing in the HCToolTip constructor.
Then, you can add each name value pair into the JSON output as they are:
foreach (Crosshairs crosshairs in crosshairsList) {
Dictionary<string, object> crosshairProps = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> prop in crosshairs.GetAllProperties()) {
crosshairProps.Add(prop.Key, prop.Value);
}
itemsList.Add(crosshairProps);
}
result["crosshairs"] = itemsList;
You might need to implement a JavaScriptConverter one level higher up, on ToolTip, in order to get an anonymous list as the property value for ToolTip.crosshairs.
I hope what this is showing though is that really all you're looking for is a key/value collection. I named the classes CrosshairsCollection and Crosshairs to try to draw the distinction between a list of <crosshairs> tags and the subtree of a <crosshairs> tag. But you could name them MultiPropertyItemCollection and MultiPropertyItem, or whatever, because there's nothing type specific in the implementation.

Categories