Nested list of constant strings in C# - c#

Any way I can store this data in a clean way, and preferably use variable names instead of strings as keys to avoid typos? E.g. UNITED_STATES = "201" instead of "United States" = "201".
{
"countries": {
"id": "123",
"data" {
"United States": "201"
"Canada": "202",
}
},
"departments": { ... }
}
I started with KeyValuePairs like this, but nesting data in here seems like a bad idea.
private static readonly List<KeyValuePair<string, string>> CategoryIds = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Countries", "123"),
new KeyValuePair<string, string>("Departments", "124")
};

Two approaches to deserialize JSON here.
Strong typing approach (good approach):
public class A
{
public B Countries {get;set;}
public C Departments {get;set;}
}
public class B
{
public int Id {get;set;}
public D Data {get;set;}
}
...
var result = JsonConvert.DeserializeObject<A>(json);
You create DTO objects manually and just expect them to deserialize successfully.
Dynamic approach (bad but sometimes acceptable approach):
dynamic result = JsonConvert.DeserializeObject(json);
var data = result.countries.data;
You create some "bag of things" (dynamic is basically a bunch of hierarchical Dictionary wrapped into syntax sugar cane), don't really care about all of them, and just want some of its properties.

Maybe you could use json.net JObject?
It allows you to work with dynamic objects and convert them to and from json strings
Documentation for JObject
https://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm
Nuget:
https://www.nuget.org/packages/Newtonsoft.Json/

You can use a dictionary<k,v> for this purpose along with a enum like below probably
enum CountryVal
{
UnitesStates,
Canada
}
With a model structure like
public class Countries
{
public string id { get; set; }
public Dictionary<CountryVal, int> Data { get; set; }
}
public class Departments
{
public string id { get; set; }
}
public class RootObject
{
public Countries countries { get; set; }
public Departments departments { get; set; }
}

You can create a public class as below and you can then call country value like CountriesConstants.UNITED_STATES in your code and if you need to change the value just update it in CountriesConstants class
public class CountriesConstants
{
public const string UNITED_STATES = "201";
public const string Canada = "202";
//Add More
}

NJsonSchema is a library that will enable you to generate code in csharp as well as few other languages from a standard json schema. It is very powerful and configurable, and can pave most of the way on your behalf. But as i said it will expect an standard json schema as for the source of generation.
var schema = NJsonSchema.JsonSchema4.FromFileAsync(filename);
var generator = new CSharpGenerator(schema.Result);
var file = generator.GenerateFile();
Above is the minimum amount of code required to generate csharp classes from json schema. you can define settings and pass to the generator function to service your special needs of course.
github page for this library:
NJsonSchema github
Nuget page:
NJsonSchema Nuget

Related

Set name of key/class using YamlDotNet or SharpYaml

I'm trying to (de)serialize following YAML using YamlDotNet or SharpYaml:
network:
IF1:
gateway4: 10.1.0.1
addresses:
- 10.1.0.10/24
- 10.1.0.20/24
IF2:
gateway4: 10.2.0.1
addresses:
- 10.2.0.10/24
- 10.2.0.20/24
I've have this C# class:
public class NetworkConfig
{
public class Network
{
//[ClassName]
public string NetworkName { get; set; }
public List<Interface> Interfaces;
public class Interface
{
//[ClassName]
public string InterfaceName { get; set; }
public string gateway4;
public List<String> addresses;
}
}
}
Instantiation:
var NetworkCfg = new NetworkConfig();
var Network = new NetworkConfig.Network();
Network.NetworkName = "network";
Network.Interfaces = new List<NetworkConfig.Network.Interface> {
new NetworkConfig.Network.Interface
{
InterfaceName = "IF1",
gateway4 = "10.1.0.1",
addresses = new List<string> { "10.1.0.10/24", "10.1.0.20/24"}
},
new NetworkConfig.Network.Interface
{
InterfaceName = "IF2",
gateway4 = "10.2.0.1",
addresses = new List<string> { "10.2.0.10/24", "10.2.0.20/24"}
}
};
How do I name the key/class Network and Interface?
One solution I could think of would be an attribute for a property, something like [ClassName] but I haven't been able to find one.
Your problem is that your YAML structure does not match your class structure. The YAML structure you show is basically
Dictionary<string, Dictionary<string, Interface>>
without InterfaceName in Interface. As you can see, the name of your network and interfaces are on another level than the objects they name, hence you cannot simply load them into the object via annotation.
You have two options:
Load the YAML into the type described above, and then post-process it to your type structure.
Implement a custom constructor and representer for your type hierarchy that (de)serializes it into the YAML structure you want.
The latter option is somewhat faster, but I'd say it's not worth the effort.

Deserializing JSON with multi-level nesting

Yet another mtgjson.com inspired question; none of the other, similar questions are getting me where I need to be. First, a couple lines of sample JSON (from mtgjson's AllPrices.json):
"00028782-6ec2-54fe-8633-2c906d8f1076": {"prices": {"mtgo": {}, "mtgoFoil": {}, "paper": {"2019-12-01": 0.15}, "paperFoil": {}}},
"00040b50-3b84-5cea-b663-70038b87fa08": {"prices": {"mtgo": {"2019-12-02": 0.02}, "mtgoFoil": {"2019-12-02": 0.02}, "paper": {"2019-12-01": 0.15}, "paperFoil": {"2019-12-01": 0.53}}}
Each parent object is a GUID and the Price Info; the Price Info is the four types of prices offered, and for each of those four types, the price data is Last Updated Date and Price.
The classes I've created (after lots of other approaches, all of which have failed):
public class price_Class
{
public string Updated { get; set; }
public decimal Price { get; set; }
}
public class PriceInfo
{
[JsonProperty("mtgo")] public price_Class mtgo { get; set; }
[JsonProperty("mtgoFoil")] public price_Class mtgof { get; set; }
[JsonProperty("paper")] public price_Class RegPrice { get; set; }
[JsonProperty("paperFoil")] public price_Class FoilPrice { get; set; }
}
And how I'm using it:
dynamic prices = JsonConvert.DeserializeObject(sJSON);
IDictionary<string, JToken> pricelist = prices;
foreach (var priceline in pricelist)
{
sUUID = priceline.Key.ToString();
PriceInfo pi = JsonConvert.DeserializeObject<PriceInfo>(priceline.Value.ToString());
Stepping through in debug mode, I see that prices seems fine; pricelist, also. The foreach defines priceline as I'd expect, and sUUID is correctly defined - but pi shows up with all four sets of price data as null - not just those that are null, but those that should have data.
priceline.value looks fine, to me:
{{
"mtgo": {},
"mtgoFoil": {},
"paper": {
"2019-12-01": 0.53
},
"paperFoil": {
"2019-12-01": 4.53
}
}}
When I expand pi in the Locals window, it shows the four classes (FoilPrice, RegPrice, mtgo, mtgof), but the contents are null.
What I need, in case it's not obvious, is to have pi.RegPrice and pi.FoilPrice defined, with a Date and Price, when that data actually exists in the JSON.
I'll admit, nested classes and JSON in general is still outside my comfort zone; I appreciate all help!
The price_Class is not adequate for deserialize your JSON object.
try with this:
public class PriceInfo
{
[JsonProperty("mtgo")] public Dictionary<string, decimal> mtgo { get; set; }
[JsonProperty("mtgoFoil")] public Dictionary<string, decimal> mtgof { get; set; }
[JsonProperty("paper")] public Dictionary<string, decimal> RegPrice { get; set; }
[JsonProperty("paperFoil")] public Dictionary<string, decimal> FoilPrice { get; set; }
}
using this tool you can find exactly what are your DTO
the problem are on your json data as i believe date are not send like this in json it come as array of integers and with specific order like day month year .
as well as you need to create your DTO similar to json even in types you can't map it till you set the same type of json in your DTO
public class Prices
{
public Mtgo mtgo { get; set; }
public MtgoFoil mtgoFoil { get; set; }
public Paper paper { get; set; }
public PaperFoil paperFoil { get; set; }
}
public class RootObject
{
public Prices prices { get; set; }
}
don't forget to tag all of them with [JsonProperty("json prop name ")]
Parsing and formatting utilities for JSON.
A central concept in lift-json library is Json AST which models the structure of a JSON document as a syntax tree.
sealed abstract class JValue
case object JNothing extends JValue // 'zero' for JValue
case object JNull extends JValue
case class JString(s: String) extends JValue
case class JDouble(num: Double) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JField(name: String, value: JValue) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValue
It comes with Lift, but non-Lift users can add lift-json as a dependency in following ways. Note, replace XXX with correct Lift version.
SBT users
Add dependency to your project description:
val lift_json = "net.liftweb" %% "lift-json" % "XXX"
Maven users
Add dependency to your pom:
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-json</artifactId>
<version>XXX</version>
</dependency>
Summary of the features:
Fast JSON parser
LINQ style queries
Case classes can be used to extract values from parsed JSON
Diff & merge
DSL to produce valid JSON
XPath like expressions and HOFs to manipulate JSON
Pretty and compact printing
XML conversions
Serialization
Low level pull parser API
Try using this for deeply nested JSONs.
It seems that the odd sub-structure of {Prices:{label:{date:amount}}} just doesn't work well with Newtonsoft's (otherwise excellent) JSON tools.
I tried the various tools (some suggested here) to generate classes; they were getting confused by the dates, creating classes for each date. I even tried generating classes for just the substring of data (priceline.value, in the example) - nope, still wouldn't work.
I ended going with a brute-force, string manipulation approach; it's ugly, I'm not exactly proud of it - but I now have what I needed. Here's the relevant snippets, just in case anyone else stumbles on the same things as I did:
private static string RemoveNoise(string input)
{
input = Regex.Replace(input, #"\r\n?|\n", string.Empty); // no more NewLine stuff
return input.Replace(" ", string.Empty)
.Replace(#"""",string.Empty);
}
...
public class PriceData
{
public string UUID { get; set; }
public string Updated { get; set; }
public string Price { get; set; }
public string FoilUpd { get; set; }
public string FoilPrc { get; set; }
}
...
string sPaperTag = #"PAPER:{";
string sPprFlTag = #"PAPERFOIL:{";
...
dynamic prices = JsonConvert.DeserializeObject(sJSON);
IDictionary pricelist = prices;
foreach (var priceline in pricelist)
{
PriceData pData = new PriceData();
pData.UUID = priceline.Key.ToString();
bool bWeHavePrice = false;
string pi = RemoveNoise(priceline.Value.ToString().ToUpper());
// parse out paper, paperFoil dates & prices manually (unusual JSON format...)
iBeg = pi.IndexOf(sPaperTag);
if (iBeg >= 0)
{
sTemp = pi.Substring(iBeg, pi.Length - iBeg);
iBeg = sTemp.IndexOf(":") + 2;
iEnd = sTemp.IndexOf("}");
sTemp = sTemp.Substring(iBeg, iEnd - iBeg); // either YYYY-MM-DD:n.nn, or an empty string
iBeg = sTemp.IndexOf(":");
if (iBeg > 0)
{
if (DateTime.TryParse(sTemp.Substring(0, iBeg), out dtTemp)) { pData.Updated = dtTemp.ToString(); bWeHavePrice = true; }
if (Decimal.TryParse(sTemp.Substring(++iBeg, sTemp.Length - iBeg), out decTemp)) { pData.Price = decTemp.ToString(); bWeHavePrice = true; }
}
}
I do that string manipulation dance again for the foil prices; I'm not currently interested in the 'mtgo' or 'mtgoFoil' data.
I'm doing all that TryParse stuff to make sure I have a valid date or amount, but I'm using the results to populate parameters in a SQLCommand, so I have to have strings; seems like extra work, going from string to Date or Decimal, then back to string - but this way I don't get exceptions when executing the SQL Insert command.
My thanks to all who helped, or tried to help. And if someone figures out how to handle it via JSON.Net, I'd love to see it!

Assign values to dynamic number of sub-classes before serializing to JSON

I am integrating with a courier that requires me to pass box dimensions for each box in my consignment to their API in JSON format. I am able to set individual properties like RecipientName, but am not sure how to pass the box details for the varying number of boxes for each consignment.
The JSON needs to look like this (example is for a 2 box consignment):
{
"RecipientName": "Joe Bloggs",
"Packages" : [{
"boxNumber": "1",
"boxHeight": 1.55,
"boxLength": 1.55,
"boxWidth": 1.55
},
{
"boxNumber": "2",
"boxHeight": 2.55,
"boxLength": 2.55,
"boxWidth": 2.55
}]
}
I have built 2 classes, one that describes the structure of the JSON, and another that contains the method to serialize the JSON.
My JSON structure class looks like this (I have used a List because I have read that arrays are a fixed length, and because the number of boxes with vary I cannot use arrays):
public class API_JSON
{
public class Rootobject
{
public string RecipientName { get; set; }
public List<Package> Packages { get; set; }
}
public class Package
{
public string boxNumber { get; set; }
public double boxHeight { get; set; }
public double boxLength { get; set; }
public double boxWidth { get; set; }
}
}
And my API methods class looks like this:
public class API_Methods
{
public string recipientName;
public List<string> boxnumber;
public List<double> boxHeight;
public List<double> boxLength;
public List<double> boxWidth;
public Boolean SubmitConsignment(out string JSONData)
{
var NewRequestObject = new API_JSON.RootObject
{
Recipient = recipientName,
Packages = new API_JSON.Package
{
foreach (string item in ContainerNumber)
{
boxNumber=???,
boxHeight=???,
boxLength???=,
boxWidth=???
}
}
}
string JSONData = JsonConvert.SerializeObject(NewRequestObject);
return true;
}
}
I am then instantiating the object, setting its public variables, then running the method list this:
API_Methods myObject = new API_Methods();
myObject.recipientName;
myObject.boxnumber.Add(1);
myObject.boxnumber.Add(2);
myObject.boxHeight.Add(1.55);
myObject.boxHeight.Add(2.55);
myObject.boxLength.Add(1.55);
myObject.boxLength.Add(2.55);
myObject.boxWidth.Add(1.55);
myObject.boxWidth.Add(2.55);
bool test = API_Methods.SubmitConsignment(out JSON);
My problem is with the foreach loop - I know the code is incomplete - but I was hoping to iterate through the lists, but even with an empty foreach loop it appears to be the wrong place to put the loop as I start getting syntax errors about an expected "}"
You're actually overcomplicating this for yourself - create complete package objects, and add them to the List Packages, and then pass the rootobject to the serializer.
The error you are getting is because you are not correctly initializing / filling your Packages List. Your object is invalid, hence the serializer is throwing exceptions.
This will be a lot easier for you if you create some constructors for your objects, something like this:
public Package(number, height, length, width)
{
boxNumber = number;
boxHeight = height;
//rest of your properties here in same format
}
You can then also make your setters private in the class, if you wish.
You can then easily create your package objects:
var package1 = new Package(10, 10, 10, 10);
This should make it a lot easier to create your list of boxes to put in your rootObject.
You can add each package to the packages list (individually or within a foreach loop):
Packages.Add(package1)
Or you could even start getting more concise:
Packages.Add(new Package(10,10,10,10));
You want to separate your concerns more to help keep this clear - so I'd recommend you fully construct your rootObject, add the packages to the list in one class (your 3rd code snippet), and then serialize it another (your 2nd code snippet).
Edit:
I think you'd find it easier to refactor your code somewhat:
1) Have a public rootobject in your Json_Api class, with get; set;. Get rid of the box collections. Get rid of your foreach loop from here too.
public class API_Methods
{
public rootObject RootObject { get; set; }
public Boolean SubmitConsignment(out string JSONData)
{
string JSONData = JsonConvert.SerializeObject(NewRequestObject);
return true;
}
}
2) Set the properties of this rootobject outside this class (where you currently initialize your objects). Add the New Package()s to Packages list here too.
API_Methods myObject = new API_Methods();
myObject.RootObject.recipientName = "NAME";
myObject.RootObject.Packages.Add(new Package(10,10,10,10);
myObject.RootObject.Packages.Add(new Package(20,20,20,20);
bool test = API_Methods.SubmitConsignment(out JSON);
3) Call the API method next, it should return a serialized version of the wholerootobject, including your packages.
Just a side note, it would be more conventional to send the RootObject as a parameter to the API, and return the Json string object back.

How to deserialize an immutable data structure?

How would I deserialize YAML to a immutable data structure?
e.g. I have this YAML:
Value: SomeString
Number: 99
And this data structure:
public class MyData
{
public MyData(string value, int number)
{
Value = value;
Number = number;
}
public string Value { get; }
public int Number { get; }
}
For this I'd to use the constructor. So somehow I'd need to first retrieve a Dictionary<string, object> parsed from the YAML respecting my class (so 99 would be int, not string), then scan my type for an appropriate constructor,
Although the question doesn't mention it, I'm assuming you are using YamlDotNet (or SharpYaml which is a fork of YamlDotNet)
YamlDotNet doesnt support deserializing into classes that do not have a default constructor - but one option to achieve what you want is to deserialize into an intermediate Builder type that is mutable which can produce the final type.
e.g.
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyData Build() => new MyData(Value, Number);
}
And then use something like:
deserializer.Deserialize<MyDataBuilder>(yaml).Build();
You would end up having to create a parallel set of builders for your whole model however, e.g. if MyData had a third parameter of type MyOtherData (I've changed the example to use records instead of classes to make it concise):
public record MyOtherData(string OtherValue);
public record MyData(string Value, int Number, MyOtherData otherData);
In which case we would need another Builder:
public class MyOtherDataBuilder
{
public string OtherValue { get; set; }
}
And MyDataBuilder would look like:
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyOtherDataBuilder MyOtherData { get; set; }
public MyData Build() => new MyData(Value, Number, MyOtherData.Build());
}
It's an old but surprisingly relevant question. Now, with records in C#, immutable collections in .net, lack of ability to deserialize immutable data is a blocker - there is no way we need to change all our data types just to be able to deserialize. One practical workaround that I found - is to convert yaml to json first, then deal with json your preferred way - System.Text.Json, Newtonsoft, etc.
Here is how to do is easiest way:
static string ConvertToJson(string yaml) {
object DeserializeYaml() =>
new DeserializerBuilder()
.Build()
.Deserialize(new StringReader(yaml))
?? throw new InvalidOperationException("Cannot deserialize yaml string:" + Environment.NewLine + yaml);
string SerializeYamlObjectToJson(object yamlObject) =>
new SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
return SerializeYamlObjectToJson(DeserializeYaml());
}
The only disadvantage, potentially big, is performance. I feel, however, that it's rarely an important requirement for yaml.
use the FormatterServices.GetUninitializedObject API (this will NOT invoke any constructors at all) and then use reflection to set fields.
Code example:
var instance = FormatterServices.GetUninitializedObject(typeof(MyData));
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var type = typeof(MyData);
var stringField = type.GetField("_value", flags);
stringField.SetValue(instance, "SomeString");
var numberField = type.GetField("_number", flags);
numberField.SetValue(instance, 99);
MyData data = (MyData)instance;

Serializing Name/Value Pairs in a Custom Object via Web Service

This is a very complicated question concerning how to serialize data via a web service call, when the data is not-strongly typed. I'll try to lay it out as best possible.
Sample Storage Object:
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<NameValuePairs> OtherInfo { get; set; }
}
[Serializable]
public class NameValuePairs {
public string Name { get; set; }
public string Value { get; set; }
}
Sample Use:
[WebMethod]
public List<StorageObject> GetStorageObjects() {
List<StorageObject> o = new List<StorageObject>() {
new StorageObject() {
Name = "Matthew",
Birthday = "Jan 1st, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
},
new StorageObject() {
Name = "Joe",
Birthday = "Jan 10th, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
}
};
return o;
}
Return Value from Web Service:
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<NameValuePairs>
<Name>Hobbies</Name>
<Value>Programming</Value>
</NameValuePairs>
<NameValuePairs>
<Name>Website</Name>
<Value>Stackoverflow.com</Value>
</NameValuePairs>
</OtherInfo>
</StorageObject>
What I want:
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
The Reason & Other Stuff:
First, I'm sorry for the length of the post, but I wanted to give reproducible code as well.
I want it in this format, because I'm consuming the web services from PHP. I want to easily go:
// THIS IS IMPORANT
In PHP => "$Result["StorageObject"]["OtherInfo"]["Hobbies"]".
If it's in the other format, then there would be no way for me to accomplish that, at all. Additionally, in C# if I am consuming the service, I would also like to be able to do the following:
// THIS IS IMPORANT
In C# => var m = ServiceResult[0].OtherInfo["Hobbies"];
Unfortunately, I'm not sure how to accomplish this. I was able to get it this way, by building a custom Dictionary that implemented IXmlSerializer (see StackOverflow: IXmlSerializer Dictionary), however, it blew the WSDL schema out of the water. It's also much too complicated, and produced horrible results in my WinFormsTester application!
Is there any way to accomplish this ? What type of objects do I need to create ? Is there any way to do this /other than by making a strongly typed collection/ ? Obviously, if I make it strongly typed like this:
public class OtherInfo {
public string Hobbies { get; set; }
public string FavoriteWebsite { get; set; }
}
Then it would work perfectly, I would have no WSDL issues, I would be able to easily access it from PHP, and C# (.OtherInfo.Hobbies).
However, I would completely lose the point of NVP's, in that I would have to know in advance what the list is, and it would be unchangeable.. say, from a Database.
Thanks everyone!! I hope we're able to come up with some sort of solution to this. Here's are the requirements again:
WSDL schema should not break
Name value pairs (NVP's) should be serialized into attribute format
Should be easy to access NVP's in PHP by name ["Hobbies"]
Should be easy to access in C# (and be compatible with it's Proxy generator)
Be easily serializable
Not require me to strongly type the data
Now, I am /completely/ open to input on a better/different way to do this. I'm storing some relatively "static" information (like Name), and a bunch of pieces of data. If there's a better way, I'd love to hear it.
This is like dynamic properties for a object.
C# is not quite a dynamic language unlike javascript or maybe PHP can parse the object properties on the fly. The following two methods are what I can think of. The second one might fit into your requirements.
The KISS Way
The Keep It Simple Stupid way
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<string> OtherInfo { get; set; }
}
You can have name value pairs which is separated by '|'
OtherInfo = {"Hobbies|Programming", "Website|Stackoverflow.com"}
Serialized forms
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<string>Hobbies|Programming</string>
<string>Website|Stackoverflow.com</string>
</OtherInfo>
</StorageObject>
The Dynamic Way in C#
Make the name value pair part become an XML element so that you can build it dynamically.
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public XElement OtherInfo { get; set; } // XmlElement for dot net 2
}
You can easily build up OtherInfo object as element centric
e.g.
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..Hobbies xelement & text value..);
OtherInfo.Add( ..WebSite xelement & text value..);
The serialized form will be
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
or build it as attribute centric
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..nvp xattribute Hobbies & value..);
OtherInfo.Add( ..nvp xattribute WebSite & value..);
<OtherInfo>
<nvp n="Hobbies" v="Programming" />
<nvp n="Website" v="Stackoverflow.com" />
</OtherInfo>
For any dynamic language, it can access to the properties directly.
For the rest, they can access the value by read the XML. Reading XML is well supported by most of framework.
This is what I've settled on.
Class Structure:
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[XmlAnyElement("Info")] // this prevents double-nodes in the XML
public XElement OtherInfo { get; set; }
}
Usage:
StorageObject o = new StorageObject();
o.OtherInfo.Add(new XElement("Hobbies","Programming");
o.OtherInfo.Add(new XElement("Website","Stackoverflow.com");
Output:
<Info>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</Info>
I would like to thank everyone for their assistance, I really appreciate the help and ideas.
As a completely different take on this, why not think about doing it completely differently. Have one web service method to return the serialized storage object, minus the OtherInfo and another method to return the list of properties (keys) for OtherInfo, and a third to return the list of values for any key. Granted, it will take more round trips to the web service if you want all of the data, but the solution will be much simpler and more flexible.
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[Nonserializable]
public Dictionary<string,List<string>> OtherInfo { get; set; }
}
[WebMethod]
public List<StorageObject> GetStorageObjects() {
// returns list of storage objects from persistent storage or cache
}
[WebMethod]
public List<string> GetStorageObjectAttributes( string name )
{
// find storage object, sObj
return sObj.Keys.ToList();
}
[WebMethod]
public List<string> GetStorageObjectAtributeValues( sting name, string attribute )
{
// find storage object, sObj
return sObj[attribute];
}
Have a look into the System.Xml.Serialization.XmlSerializerAssemblyAttribute attribute. This lets you specify a custom class-level serializer. You'll be able to spit out whatever XML you like.
A quick way to get up to speed on these is to use sgen.exe to generate one and have a peek at it with Reflector.
-Oisin
I'm not sure this would solve your problem (it would in C#, but maybe not in PHP), but try using Dictionary<string,List<string>> OtherInfo instead of List<NameValuePairs>. Then "Hobbies" and "Websites" would be your keys and the values would be the list of hobbies or web sites. I'm not sure how it would serialize, though.
You would be able to reference the lists of hobbies as:
List<string> hobbies = storageObject.OtherInfo["Hobbies"];
[EDIT] See here for a generic XML serializable dictionary. This derived class is the one you would need to use instead of generic Dictionary.

Categories