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

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.

Related

Nested list of constant strings in 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

Get tags names from json file as a variable in a class

I am using C# MVC
I am importing a Json file from a location to an Rest API. In order to display the data. the program needs to know the name of the tags.
I would like to know if there is a way to get the tags from the files in an array of strings and declare them via program instead of manually inputting them.
Current C# Code
public class Person
{
public int Id { get; set; }
public int SomeID { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Current Json File:
[{"Id":212,"SomeID":71,"Number":0,"Name":"Mr Jones Mones","Address":"21, street, city"}
So as mentioned this code words fine. But, as I am importing data, the tags may contain different information and I would like to have the public declaration in a way that I can import any file to the API and be able to edit and export it.
Thank you
If you use JSON.net you can parse out json into a JObject then iterate over it's properties like so, however each property value can be any type of object so you'd need to write something to either recursively listed out nested objects or check if values are strings and only take them if they are etc.
var obj = JObject.Parse("{ \"test\": \"test\", \"test1\": \"test1\", \"test2\": \"test2\" }");
foreach (var prop in obj.Properties())
{
Console.WriteLine(prop.Name);
}
You can use JsonConvert like this:
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
Read the docs for JsonConvert for more info.

FileHelpers error: The field: 'k__BackingField' has the type: XXX that is not a system type, so this field need a CustomConverter

I need to read a CSV file with FileHelpers based on type, automatically generated by my MVC model. The model looks like this:
public partial class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
The last field is obviously generated by a foreign key in database, referring to table MerchantCategories.
Then I attempt to create an instance of FileHelperEngine with this type:
var engine = new FileHelperEngine<Merchant>();
And get the following exception:
The field: 'k__BackingField' has the type: MerchantCategory that is not a system type, so this field need a CustomConverter ( Please Check the docs for more Info).
Actually I don't need this field at all for my import, so I tried to ignore it in derived class:
[DelimitedRecord(",")]
public class MerchantForImport : Merchant {
[FieldHidden]
new public MerchantCategory MerchantCategory;
}
var engine = new FileHelperEngine<MerchantForImport>();
And still the same error. I don't need this field at all, I don't want to implement any FieldConverter for it, I never asked for this k__BackingField and it's nowhere to be found in my code!
I can't call FileHelperEngine.Options.RemoveField() because the exception is thrown by the constructor.
Where does that come from? How do I get rid of it?
From a design perspective, I think you are going about it the wrong way. You are trying to use the Merchant class for two incompatible uses. Instead you should have two separate classes.
FileHelpers is a library for describing csv files so that you can import them easily. You should have a MerchantFileSpec for describing your file. It's really not a proper C# class - it may have: dummy fields to represent unused columns; lots of attributes [FieldNullValue], [FieldQuoted], [FieldConverter]; etc. It works best with public fields (a FileHelpers limitation which is not C# best practice), etc. It is a convenience syntax for describing the import file. It should not include any business logic or special constructors, or backing fields. Keep it as simple as possible.
Then you can have your MVC-generated Merchant class which is separate. Its purpose is to describe the merchant as required by the MVC framework, with foreign keys, ids, whatever.
Then you use a FileHelperEngine<MerchantFileSpec> to read the records into an array and map it to an enumerable of Merchant (via Linq or a library like AutoMapper).
Something like:
/// Your MVC-generated class. Add methods, getters, setters, whatever.
/// FileHelpers doesn't use this class.
class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
/// This is the class FileHelpers will use
/// This class describes the CSV file only. Stick to whatever
/// syntax conventions are required by FileHelpers.
[DelimitedRecord(";")]
class ProductMerchantFileSpec
{
[FieldQuoted(QuoteMode.OptionalForRead)]
public long Id;
[FieldQuoted(QuoteMode.OptionalForRead)]
public string Name;
[FieldQuoted(QuoteMode.OptionalForRead)]
// Handle non-US formats such as , decimal points
// convert from inches to centimetres?
// you get the idea...
[FieldConverter(MyCustomizedCategoryConverter)] // you get the idea
public int Category;
}
class Program
{
static void Main(string[] args)
{
var engine = new FileHelperEngine<ProductMerchantFileSpec>();
var productMerchantRecords = engine.ReadFile(filePath);
var productMerchants = productMerchantRecords
.Select(x => new Merchant() { Id = x.Id, Name = x.Name, Category = x.Category });
}
}
I received this error specifically because my object (i.e. Merchant) was missing a column that existed in the source file. I was able to work around the issue prior to realizing the missing column by adding a new property to my object class public string[] MyProperty { get; set; }. This work-around help me realize a column was missing.
i.e..
public partial class Merchant
{
public long id { get; set; }
..
..
..
public string[] MyProperty { get; set; }
}

How to solve "Expected element name to be '_t', not 'number'."

I have a mongo model like this:
class ObjectA {
[BsonId(IdGenerator = typeof(BsonObjectIdGenerator))]
public BsonObjectId Id;
[BsonElement("number")]
public int Number { get; set; }
[BsonElement("b")]
public List<ObjectB> objectB { get; set; }
}
class ObjectB {
[BsonElement("someProperty")]
public string SomeProperty { get; set; }
}
My problem is when I aggregate the collection with {$unwind:objectB}. The result documencts have a unique object on the property objectB (not a list).
So the cast failes with the exception:
An error occurred while deserializing the ObjectB property of class
ObjectA: Expected element name to be '_t', not
'number'.
Do I have to create a new model for this or is there a easier way to solve it?
You could also choose to work with BsonDocument directly (but that is not strongly typed and more cumbersome to work with), e.g. (I'm using the simple Posts/Tags example here)
var aggregationResults = db.GetCollection("Posts").Aggregate().ResultDocuments;
foreach (var document in aggregationResults)
{
var tag = document.GetValue("Tags").AsString;
}
Unlike the normal query and projection operators, the aggregation framework may change the structure of your document. As you already pointed out, $unwind transforms a document that contains an array into a number of documents that each have a single value of the same name.
Another approach this is to indeed create a new type for this, so
class Post {
public List<string> Tags { get; set; }
...
would become
class PostAggregationResult {
public string Tags { get; set; }
...
That is very easy to work with, but if you have very various aggregation queries, you need a large number of classes which can be annoying.

Serializing object to XML to get list of tags named like field value

I have 2 classes:
public class LocalizationEntry
{
public List<TranslationPair> Translations
{
get;
set;
}
}
public class TranslationPair
{
public string Language
{
get;
set;
}
public string Translation
{
get;
set;
}
}
Is it possible to get such a XML using standard serializer?
<LocalizationEntry>
<Translations>
<EN>apple<EN>
<PL>jabłko<PL>
<DE>apfel<DE>
</Translations>
</LocalizationEntry>
I was thinking about something like XmlArrayItem(ElementName=this.Language) attribute but of course this construction is illegal apart from that attribute value cannot be determined in runtime.
Thanks for any help. Surely I can use other structure, but I'm curious is it possible.
To customise the element names is a pain; I would forget about XmlSerializer here, as you'd have to use IXmlSerializable which is a royal pain. I'd use XElement instead:
var source = new LocalizationEntry {
Translations = new List<TranslationPair> {
new TranslationPair {Language="EN", Translation="apple"},
new TranslationPair {Language="PL", Translation="jabłko"},
new TranslationPair {Language="DE", Translation="apfel"},
}
};
var el = new XElement("LocalizationEntry",
new XElement("Translations",
from pair in source.Translations
select new XElement(pair.Language, pair.Translation)));
var xml = el.ToString();
enter link description hereUse the XMLSerializer Object in the .NET Framework
Remember to put the Serializable Attribute on the Class Name
click this Link, i cant work out how to Add the Name of the URL sorry

Categories