I am working on a small project to learn how to serialize an object in C#.NET I created the following class that is the class I am attempting to serialize:
public class Object
{
private Dictionary<string, int> dictionaryStringInt;
public Object()
{
this.dictionaryStringInt = new Dictionary<string, int>();
}
public void AddToDictionary(string s, int i)
{
this.dictionaryStringInt.Add(s, i);
}
public List<DictionaryEntry> DictionaryStringInt
{
get
{
List<DictionaryEntry> list = new List<DictionaryEntry>();
foreach (KeyValuePair<string, int> element in dictionaryStringInt)
{
list.Add(new DictionaryEntry(element.Key, element.Value));
}
return list;
}
set
{
Dictionary<string, int> dictionary = new Dictionary<string, int>();
foreach (DictionaryEntry entry in value)
{
dictionary.Add(entry.Key, entry.Value);
}
dictionaryStringInt = dictionary;
}
}
public class DictionaryEntry
{
private string key;
private int value;
public DictionaryEntry()
{
this.key = string.Empty;
this.value = 0;
}
public DictionaryEntry(string key, int value)
{
this.key = key;
this.value = value;
}
[XmlAttribute("Key")]
public string Key
{
get
{
return key;
}
set
{
key = value;
}
}
[XmlText]
public int Value
{
get
{
return value;
}
set
{
this.value = value;
}
}
}
}
And I have this code here that does the work:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
this.ClearTextBox();
SerialisationTest.Object #object = this.SetUpObject();
this.Serialize(#object);
#object = null;
#object = this.Deserialise(#"C:\Sources\SerialisationTest\test.xml");
this.textBox1.Text = #object.ToString();
this.DisplayXML(#object);
}
public void Serialize(SerialisationTest.Object #object)
{
XmlSerializer serializer = new XmlSerializer(typeof(SerialisationTest.Object));
using (TextWriter writer = new StreamWriter(#"C:\Sources\SerialisationTest\test.xml"))
{
serializer.Serialize(writer, #object);
}
}
public SerialisationTest.Object Deserialise(string path)
{
XmlSerializer serializer = new XmlSerializer(typeof(SerialisationTest.Object));
using (TextReader reader = new StreamReader(path))
{
return (SerialisationTest.Object)serializer.Deserialize(reader);
}
}
public void DisplayXML(SerialisationTest.Object #object)
{
XmlSerializer serializer = new XmlSerializer(typeof(SerialisationTest.Object));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, #object);
this.textBox1.Text = writer.ToString();
}
}
public SerialisationTest.Object SetUpObject()
{
SerialisationTest.Object #object = new SerialisationTest.Object();
#object.AddToDictionary("1", 1);
#object.AddToDictionary("2", 2);
#object.AddToDictionary("3", 3);
return #object;
}
public void ClearTextBox()
{
this.textBox1.Text = "";
}
}
Now here is the result I see in my textbox:
<?xml version="1.0" encoding="utf-16"?>
<Object xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DictionaryStringInt />
</Object>
Now most of the code is just me playing around trying to serialize things. But then I tried serializing the DictionaryStringInt and saw on MSDN that Dictionary cannot be serialize. So I tried to cheat the serializer by having the property return a list of DictionaryEntry and converting this list into a dictionary in the setter. But as you can see, the DictionaryStringInt's data is not serialized. I was hoping to received something like:
<?xml version="1.0" encoding="utf-16"?>
<Object xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DictionaryStringInt>
<DictionaryEntry Key="1">1</DictionaryEntry>
<DictionaryEntry Key="2">2</DictionaryEntry>
<DictionaryEntry Key="3">3</DictionaryEntry>
</DictionaryStringInt>
</Object>
Does anyone know how to achieve this? (BTW I am aware that if I have multiple of the same key in the XML when deserializing the dictionary I'll get something like an KeyAlreadyPresentException of some sort and this is a behavior want)
Thanks!
Edit
I just found out that the "C:\Sources\SerialisationTest\test.xml" contains the right result.
So there seems to be a problem with the deserialisation or the displaying of the serialisation using the StringWriter.
Is there something I am doing wrong?
Edit2
Simplified the code
Edit3
I looked at the answer linked in the comment and I think I got what is going on... In one of the answers there is a comment that point that the setter wont be called and that I should use an array instead. I'm looking into this.
The problem is that you are losing the contents of your surrogate List<DictionaryEntry> DictionaryStringInt property during deserialization. The workaround is to change it to an array:
DictionaryEntry [] DictionaryStringInt
{
get
{
if (dictionaryStringInt == null)
return null;
List<DictionaryEntry> list = new List<DictionaryEntry>();
foreach (KeyValuePair<string, int> element in dictionaryStringInt)
{
list.Add(new DictionaryEntry(element.Key, element.Value));
}
return list.ToArray();
}
set
{
if (value == null)
return;
Dictionary<string, int> dictionary = new Dictionary<string, int>();
foreach (DictionaryEntry entry in value)
{
dictionary.Add(entry.Key, entry.Value);
}
dictionaryStringInt = dictionary;
}
}
For an explanation of why a List<DictionaryEntry> surrogate property fails during deserialization, see Cannot deserialize XML into a list using XML Deserializer. As explained there:
XmlSerializer calls the getter to get the list. If null, it allocates a list and sets it via the setter. It holds onto the list in some local variable while reading it.
It deserializes each list element, and adds it to the list it is holding.
And that's it. It never calls the containing class's list property setter afterwards. Thus the contents of the list are never populated into your dictionary.
But, since an array cannot be resized once allocated, it must be allocated and set back after its contents are read and deserialized. Thus, an array property can function as a surrogate during deserialization.
Related
I want to save an object containing a value that can basically be any type. I am using XmlSerializer to do that and it works fine with one exception: If the value is an enum, the serializer stores the value as integer. If I load it back and use that value to read from a dictionary I get a KeyNotFoundException.
Is there any elegant way to save the enum as enum or to avoid the KeyNotFoundException and still use XmlSerializer? (Casting back to enum is not a good option here, the container and dictionary must support all types)
Here is a simplified piece of code that demonstrates the problem:
public enum SomeEnum
{
SomeValue,
AnotherValue
}
// Adding [XmlInclude(typeof(SomeEnum))] is no proper solution as Key can be any type
public class GenericContainer
{
public object Key { get; set; }
}
private Dictionary<object, object> SomeDictionary = new Dictionary<object, object>();
public void DoSomething()
{
SomeDictionary[SomeEnum.AnotherValue] = 123;
var value = SomeDictionary[SomeEnum.AnotherValue];
Save(new GenericContainer { Key = SomeEnum.AnotherValue}, "someFile.xml");
var genericContainer = (GenericContainer)Load("someFile.xml", typeof(GenericContainer));
// Throws KeyNotFoundException
value = SomeDictionary[genericContainer.Key];
}
public void Save(object data, string filePath)
{
var serializer = new XmlSerializer(data.GetType());
using (var stream = File.Create(filePath))
{
serializer.Serialize(stream, data);
}
}
public object Load(string filePath, Type type)
{
var serializer = new XmlSerializer(type);
using (var stream = File.OpenRead(filePath))
{
return serializer.Deserialize(stream);
}
}
You can put attributes to your enum
public enum Simple
{
[XmlEnum(Name="First")]
one,
[XmlEnum(Name="Second")]
two,
[XmlEnum(Name="Third")]
three,
}
Original:
How do you use XMLSerialize for Enum typed properties in c#?
I have a generic type Foo1 which is basically a container with some metadata and a generic list. When I serialize that type, it gets the name "Foo1Of" + TypeParameterName.
public class Foo1<T>
{
public string Name { get; set; } = string.Empty;
public List<T> List { get; set; } = new List<T>();
}
[TestMethod]
public void SerializeFoo1()
{
Foo1<string> foo = new Foo1<string>
{
Name = "Foo1",
List =
{
"Bar",
"Baz"
}
};
XmlSerializer s = new XmlSerializer(foo.GetType());
StringBuilder sb = new StringBuilder();
XmlWriter w = XmlWriter.Create(sb);
s.Serialize(w, foo);
Console.WriteLine(sb.ToString());
}
This is the old version and returns:
<?xml version="1.0" encoding="utf-16"?>
<Foo1OfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Foo1</Name>
<List><string>Bar</string><string>Baz</string></List>
</Foo1OfString>
To access the internal list more directly I implemented IEnumerable, IEnumerable and an Add(...) method.
public class Foo2<T> : IEnumerable<T>, IEnumerable
{
public string Name { get; set; } = string.Empty;
public List<T> List { get; set; } = new List<T>();
public void Add(T item)
{
List.Add(item);
}
public IEnumerator<T> GetEnumerator()
{
return List.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return List.GetEnumerator();
}
}
[TestMethod]
public void SerializeFoo2()
{
Foo2<string> foo = new Foo2<string>
{
Name = "Foo2",
List =
{
"Bar",
"Baz"
}
};
XmlSerializer s = new XmlSerializer(foo.GetType());
StringBuilder sb = new StringBuilder();
XmlWriter w = XmlWriter.Create(sb);
s.Serialize(w, foo);
Console.WriteLine(sb.ToString());
}
After doing that, XML Serialization behaves very strange:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>Bar</string><string>Baz</string>
</ArrayOfString>
The name becomes "ArrayOf" + TypeParameterName and the property "Name" vanishes. The XmlSerializer seems to think, that my custom type is an array. Is there a way to change this behaviour which means to be able to implement the interfaces and Add(...) but get the old XML serialization?
This is default behavior of XmlSerializer as you are implementing IEnumerable<T> interface. Due to IEnumerable<string> type, XmlSerializer thinks it is a collection/array of string.
If you must implemented IEnumerable<T>, then one way to overcome this default behavior is to implement custom serialization. Implement IXmlSerializable for your Foo1<T>. You can then customize the Xml graph as you wish, and return your desired Name and List. XmlSerializer will then your custom implementation.
I am currently developing a software that will be used by users that should not be able to access the back-end of it all but should still be able to easily change configuration/settings for the application.
I decided the best approach would be a custom "configuration file (.cfg)" located in the root of the final build.
Simple example of the .cfg file:
serveraddress='10.10.10.10'
serverport='1234'
servertimeout='15000'
Since I wanted the configuration file to easily be extended I decided to use some custom attributes and some simple LINQ.
This does work like I expect it to, but since I am still a novice in .net I am afraid I have not gone with the best approach and my question is therefor:
Is there anything I can do to improve this?
Or is there just generally a better approach for this?
This is my code for reading the configuration file and assigning the values to it's corresponding properties.
ConfigFileHandler.cs
public void ReadConfigFile()
{
var cfgFile = new ConfigFile();
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToList();
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attrs = (ConfigFileFieldAttribute[])prop.GetCustomAttributes
(typeof(ConfigFileFieldAttribute), false);
foreach (var t in from attr in attrs from t in testList where t.Item1 == attr.Name select t)
{
prop.SetValue(cfgFile, t.Item2);
}
}
}
ConfigFile.cs
class ConfigFile
{
private static string _serverAddress;
private static int _serverPort;
private static int _serverTimeout;
[ConfigFileField(#"serveraddress")]
public string ServerAddress
{
get { return _serverAddress; }
set { _serverAddress= value; }
}
[ConfigFileField(#"serverport")]
public string ServerPort
{
get { return _serverPort.ToString(); }
set { _serverPort= int.Parse(value); }
}
[ConfigFileField(#"servertimeout")]
public string ServerTimeout
{
get { return _serverTimeout.ToString(); }
set { _serverTimeout= int.Parse(value); }
}
}
any tips on writing better looking code would be highly appreciated!
UPDATE:
Thanks for all the feedback.
Below is the final classes!
https://dotnetfiddle.net/bPMnJA for a live example
Please note, this is C# 6.0
ConfigFileHandler.cs
public class ConfigFileHandler
{
public void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var configDictionary = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
ConfigFile.SetDictionary(configDictionary);
}
}
ConfigFile.cs
public class ConfigFile
{
private static Dictionary<string, string> _configDictionary;
public string ServerAddress => PullValueFromConfig<string>("serveraddress", "10.1.1.10");
public int ServerPort => PullValueFromConfig<int>("serverport", "3306");
public long ServerTimeout => PullValueFromConfig<long>("servertimeout", "");
private static T PullValueFromConfig<T>(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value) && value.Length > 0)
return (T) Convert.ChangeType(value, typeof (T));
return (T) Convert.ChangeType(defaultValue, typeof (T));
}
public static void SetDictionary(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
}
You could keep the simplicity of your config file and get rid of the nested loops by loading the values into a dictionary and then passing that into your ConfigFile class.
public static void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
var cfgFile = new ConfigFile(testList);
}
The new ConfigFile class:
class ConfigFile
{
private Dictionary<string, string> _configDictionary;
public ConfigFile(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
public string ServerAddress
{
get { return PullValueFromConfig("serveraddress", "192.168.1.1"); }
}
public string ServerPort
{
get { return PullValueFromConfig("serverport", "80"); }
}
public string ServerTimeout
{
get { return PullValueFromConfig("servertimeout", "900"); }
}
private string PullValueFromConfig(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
}
I decided to use a custom "configuration file (.cfg)" located in the root of the final build.
Good idea. For cleaner code, you could use JSON and JSON.NET for de/serialization and put the read/write into the ConfigFile class. Here is an example that is live as a fiddle.
The ConfigFile class is responsible for loading and saving itself and uses JSON.NET for de/serialization.
public class ConfigFile
{
private readonly static string path = "somePath.json";
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string ServerTimeout { get; set; }
public void Save()
{
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(path, json)
}
public static ConfigFile Load()
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
Here is how you would use it to load the file, change its properties, and save.
ConfigFile f = ConfigFile.Load();
f.ServerAddress = "0.0.0.0";
f.ServerPort = "8080";
f.ServerTimeout = "400";
f.Save();
We use the .json file extension as a convention. You could still use .cfg because it's just plain text with a specific syntax. The resultant config file content from the above usage is this:
{
"ServerAddress":"0.0.0.0",
"ServerPort":"8080",
"ServerTimeout":"400"
}
You could just tell your clients to "change the numbers only". Your approach is fine, as far as I'm concerned. The above is just a cleaner implementation.
Firstly, I would do what Phil did, and store your testlist in a Dictionary.
var configLines = File.ReadAllLines("configfile.cfg");
var testDict = configLines.Select(line => line.Split('=', 2))
.ToDictionary(s => s[0], s => s[1].Replace("'", ""));
Then you can clean up the property assignment LINQ a bit:
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attr = prop.GetCustomAttributes(false)
.OfType<ConfigFileFieldAttribute>()
.FirstOrDefault();
string val;
if (attr != null && testDict.TryGetValue(attr.Name, out val))
prop.SetValue(cfgFile, val);
}
You might even be able to call:
var attr = prop.GetCustomAttributes<ConfigFileFieldAttribute>(false).FirstOrDefault();
Don't have an IDE on me so I can't check right now
Given following json result:
The default json result has a known set of fields:
{
"id": "7908",
"name": "product name"
}
But can be extended with additional fields (in this example _unknown_field_name_1 and _unknown_field_name_2) of which the names are not known when requesting the result.
{
"id": "7908",
"name": "product name",
"_unknown_field_name_1": "some value",
"_unknown_field_name_2": "some value"
}
I would like the json result to be serialized and deserialized to and from a class with properties for the known fields and map the unknown fields (for which there are no properties) to a property (or multiple properties) like a dictionary so they can be accessed and modified.
public class Product
{
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, string> fields { get; set; }
}
I think I need a way to plug into a json serializer and do the mapping for the missing members myself (both for serialize and deserialize).
I have been looking at various possibilities:
json.net and custom contract resolvers (can't figure out how to do it)
datacontract serializer (can only override onserialized, onserializing)
serialize to dynamic and do custom mapping (this might work, but seems a lot of work)
let product inheriting from DynamicObject (serializers work with reflection and do not invoke the trygetmember and trysetmember methods)
I'm using restsharp, but any serializer can be plugged in.
Oh, and I cannot change the json result, and this or this didn't help me either.
Update:
This looks more like it: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx
An even easier option to tackling this problem would be to use the JsonExtensionDataAttribute from JSON .NET
public class MyClass
{
// known field
public decimal TaxRate { get; set; }
// extra fields
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
There's a sample of this on the project blog here
UPDATE Please note this requires JSON .NET v5 release 5 and above
See https://gist.github.com/LodewijkSioen/5101814
What you were looking for was a custom JsonConverter
This is a way you could solve it, although I don't like it that much. I solved it using Newton/JSON.Net. I suppose you could use the JsonConverter for deserialization aswell.
private const string Json = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";
[TestMethod]
public void TestDeserializeUnknownMembers()
{
var #object = JObject.Parse(Json);
var serializer = new Newtonsoft.Json.JsonSerializer();
serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error;
serializer.Error += (sender, eventArgs) =>
{
var contract = eventArgs.CurrentObject as Contract ?? new Contract();
contract.UnknownValues.Add(eventArgs.ErrorContext.Member.ToString(), #object[eventArgs.ErrorContext.Member.ToString()].Value<string>());
eventArgs.ErrorContext.Handled = true;
};
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(Json)))
using (StreamReader streamReader = new StreamReader(memoryStream))
using (JsonReader jsonReader = new JsonTextReader(streamReader))
{
var result = serializer.Deserialize<Contract>(jsonReader);
Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_1"));
Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_2"));
}
}
[TestMethod]
public void TestSerializeUnknownMembers()
{
var deserializedObject = new Contract
{
id = 7908,
name = "product name",
UnknownValues = new Dictionary<string, string>
{
{"_unknown_field_name_1", "some value"},
{"_unknown_field_name_2", "some value"}
}
};
var json = JsonConvert.SerializeObject(deserializedObject, new DictionaryConverter());
Console.WriteLine(Json);
Console.WriteLine(json);
Assert.AreEqual(Json, json);
}
}
class DictionaryConverter : JsonConverter
{
public DictionaryConverter()
{
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Contract);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = value as Contract;
var json = JsonConvert.SerializeObject(value);
var dictArray = String.Join(",", contract.UnknownValues.Select(pair => "\"" + pair.Key + "\":\"" + pair.Value + "\""));
json = json.Substring(0, json.Length - 1) + "," + dictArray + "}";
writer.WriteRaw(json);
}
}
class Contract
{
public Contract()
{
UnknownValues = new Dictionary<string, string>();
}
public int id { get; set; }
public string name { get; set; }
[JsonIgnore]
public Dictionary<string, string> UnknownValues { get; set; }
}
}
I thought I'd throw my hat in the ring since I had a similar problem recently. Here's an example of the JSON I wanted to deserialize:
{
"agencyId": "agency1",
"overrides": {
"assumption.discount.rates": "value: 0.07",
".plan": {
"plan1": {
"assumption.payroll.growth": "value: 0.03",
"provision.eeContrib.rate": "value: 0.35"
},
"plan2": {
".classAndTier": {
"misc:tier1": {
"provision.eeContrib.rate": "value: 0.4"
},
"misc:tier2": {
"provision.eeContrib.rate": "value: 0.375"
}
}
}
}
}
}
This is for a system where overrides apply at different levels and are inherited down the tree. In any case, the data model I wanted was something that would allow me to have a property bag with these special inheritance rules also supplied.
What I ended up with was the following:
public class TestDataModel
{
public string AgencyId;
public int Years;
public PropertyBagModel Overrides;
}
public class ParticipantFilterModel
{
public string[] ClassAndTier;
public string[] BargainingUnit;
public string[] Department;
}
public class PropertyBagModel
{
[JsonExtensionData]
private readonly Dictionary<string, JToken> _extensionData = new Dictionary<string, JToken>();
[JsonIgnore]
public readonly Dictionary<string, string> Values = new Dictionary<string, string>();
[JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByPlan;
[JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByClassAndTier;
[JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, PropertyBagModel> ByBarginingUnit;
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
foreach (var kvp in Values)
_extensionData.Add(kvp.Key, kvp.Value);
}
[OnSerialized]
private void OnSerialized(StreamingContext context)
{
_extensionData.Clear();
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
Values.Clear();
foreach (var kvp in _extensionData.Where(x => x.Value.Type == JTokenType.String))
Values.Add(kvp.Key, kvp.Value.Value<string>());
_extensionData.Clear();
}
}
The basic idea is this:
The PropertyBagModel on deserialization by JSON.NET has the ByPlan, ByClassAndTier, etc. fields populated and also has the private _extensionData field populated.
Then JSON.NET calls the private OnDeserialized() method and that will move the data from _extensionData to Values as appropriate (or drop it on the floor otherwise - presumably you could log this if it was something you wanted to know). We then remove the extra gunk from _extensionData so it doesn't consume memory.
On serialization, the OnSerializing method gets calls where we move stuff into _extensionData so it gets saved.
When serialization has finished, OnSerialized gets called and we remove the extra stuff from _extensionData.
We could further delete and recreate the _extensionData Dictionary when needed but I didn't see a real value in this as I'm not using tons of these objects. To do this we'd just create on OnSerializing and delete on OnSerialized. On OnDeserializing, instead of clearing, we could free it.
I was looking into a similar issue and found this post.
Here is a way to do it using reflection.
To make it more generic, one should check the type of the property instead of simply using ToString() in propertyInfo.SetValue, unless OFC all the actual properties are strings.
Also, lowercase property names is not standard in C# but given that GetProperty is case sensitive there are few other options.
public class Product
{
private Type _type;
public Product()
{
fields = new Dictionary<string, object>();
_type = GetType();
}
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, object> fields { get; set; }
public void SetProperty(string key, object value)
{
var propertyInfo = _type.GetProperty(key);
if (null == propertyInfo)
{
fields.Add(key,value);
return;
}
propertyInfo.SetValue(this, value.ToString());
}
}
...
private const string JsonTest = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";
var product = new Product();
var data = JObject.Parse(JsonTest);
foreach (var item in data)
{
product.SetProperty(item.Key, item.Value);
}
I have a very very strange problem. I would like to serialize a object via xml.
Ok so I started creating a XmlSerializer and there was no error - nothing.
But if i deserialize... the properties are just not set. Another strange thing is that the xml seems to be ok.
this is a part of my code the rest are just methods...
[XmlInclude(typeof(TestMapper))]
[XmlInclude(typeof(TestSource))]
[XmlInclude(typeof(TestTarget))]
public abstract class ElementBase : IElement
{
[XmlIgnore]
public IElement this[int index]
{
get
{
return Childs.ElementAt(index).Value;
}
}
List<ElementBase> _childSerializable;
[XmlArrayItem(typeof(ElementBase))]
public List<ElementBase> ChildSerializable
{
get
{
_childSerializable = Childs.Select(x => x.Value).ToList();
return _childSerializable;
}
set
{
_childSerializable = value;
foreach (ElementBase element in _childSerializable)
Childs.Add(element.Index, element);
}
}
protected Dictionary<object, ElementBase> _childs;
[XmlIgnore]
public Dictionary<object, ElementBase> Childs
{
get
{
return _childs ?? (_childs = new Dictionary<object ,ElementBase>());
}
set
{
_childs = value;
}
}
object _index = -1;
public object Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
And this is how i serialize:
TestTarget target = new TestTarget();
TestSource source = new TestSource() { Value = "Hallo", Index = 1};
TestSource source1 = new TestSource() { Value = " Welt", Index = 2};
TestMapper mapper = new TestMapper();
target.ConnectChild(mapper);
mapper.ConnectChild(source);
mapper.ConnectChild(source1);
target.Execute();
System.IO.MemoryStream memStream = new System.IO.MemoryStream();
XmlSerializer serializer = new XmlSerializer(target.GetType());
serializer.Serialize(memStream, target);
memStream.Position = 0;
object obj = serializer.Deserialize(memStream);
target = obj as TestTarget;
memStream.Position = 0;
Console.ReadKey();
Ok so now and this is the final result:
<?xml version="1.0"?>
<TestTarget xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ChildSerializable>
<ElementBase xsi:type="TestMapper">
<ChildSerializable>
<ElementBase xsi:type="TestSource">
<ChildSerializable />
<Index xsi:type="xsd:int">1</Index>
<Value xsi:type="xsd:string">Hallo</Value>
</ElementBase>
<ElementBase xsi:type="TestSource">
<ChildSerializable />
<Index xsi:type="xsd:int">2</Index>
<Value xsi:type="xsd:string"> Welt</Value>
</ElementBase>
</ChildSerializable>
<Index xsi:type="xsd:int">-1</Index>
</ElementBase>
</ChildSerializable>
<Index xsi:type="xsd:int">-1</Index>
</TestTarget>
So does anyone has any idea what happend wrong. I am working since a few hours on it but i can t find anything :((
It seems to be related to the fact that you are using a List<ElementBase>. If you use a simple array like ElementBase[], the items are deserialized.
There are some explanation here, but nothing I found satisfactory.