I wanted to create a class and use a method from the initialized class to change the property values of the calling instance. Somehow I have a knot in my brain and there seems to be a basic thinking error of mine. Maybe someone can help me figure it out.
Class Program
{
...
private void Initialize()
{
Zoo myZoo = new Zoo();
myZoo.Load();
Console.WriteLine(myZoo.ZooName);
}
}
and the Zoo-Class:
public class Zoo
{
public string ZooName { get; set; }
...
internal void Load()
{
Zoo myZoo = this;
using (StreamReader reader = File.OpenText(#"C:\Areas.json"))
{
JsonSerializer serializer = new JsonSerializer();
myZoo = (Zoo) serializer.Deserialize(reader, typeof(Zoo));
}
}
}
The JSON part works fine, but as soon as the Load()-method comes to an end the myZoo/this is set to NULL. Is there any possibility to use 'this' to modify the property values of the calling class instance?
You probably want to make a factory method on your class instead. This function will return a new instance of Zoo with the data from your json file.
Like this:
public class Zoo
{
public string ZooName { get; set; }
...
public static Zoo Init()
{
using (StreamReader reader = File.OpenText(#"C:\Areas.json"))
{
JsonSerializer serializer = new JsonSerializer();
var myZoo = (Zoo) serializer.Deserialize(reader, typeof(Zoo));
return myZoo;
}
}
}
In your Initialize function you can now create an instance like this:
private void Initialize()
{
var myZoo = Zoo.Init();
Console.WriteLine(myZoo.ZooName);
}
other way out.you can use ref keyword for the same.
Class Program
{
...
private void Initialize()
{
Zoo myZoo = new Zoo();
myZoo.Load(ref myZoo);
Console.WriteLine(myZoo.ZooName);
}
}
public class Zoo
{
public string ZooName { get; set; }
...
internal void Load(ref Zoo myZoo)
{
using (StreamReader reader = File.OpenText(#"C:\Areas.json"))
{
JsonSerializer serializer = new JsonSerializer();
myZoo = (Zoo) serializer.Deserialize(reader, typeof(Zoo));
}
}
}
You cannot set the this pointer. There also is no assignment operator in C# to overload.
You could copy all properties from your loaded zoo object into the this object.
A more common approach is to have a static factory method to do this for you:
public class Zoo
{
public string ZooName { get; set; }
...
public static Zoo Load(string file)
{
using (StreamReader reader = File.OpenText(file))
{
JsonSerializer serializer = new JsonSerializer();
return (Zoo) serializer.Deserialize(reader, typeof(Zoo));
}
}
}
Later call it like this:
Zoo z = Zoo.Load(#"C:\Areas.json");
Overview
I'm trying to write a web service using ASP.NET Core that allows clients to query and modify the state of a microcontroller. This microcontroller contains a number of systems that I model within my application - for instance, a PWM system, an actuator input system, etc.
The components of these systems all have particular properties that can be queried or modified using a JSON patch request. For example, the 4th PWM on the micro can be enabled using an HTTP request carrying {"op":"replace", "path":"/pwms/3/enabled", "value":true}. To support this, I'm using the AspNetCore.JsonPatch library.
My problem is that I'm trying to implement JSON Patch support for a new "CAN database" system that logically should map a definition name to a particular CAN message definition, and I'm not sure how to go about this.
Details
The diagram below models the CAN database system. A CanDatabase instance should logically contain a dictionary of the form IDictionary<string, CanMessageDefinition>.
To support creating new message definitions, my application should allow users to send JSON patch requests like this:
{
"op": "add",
"path": "/candb/my_new_definition",
"value": {
"template": ["...", "..."],
"repeatRate": "...",
"...": "...",
}
}
Here, my_new_definition would define the definition name, and the object associated with value should be deserialised to a CanMessageDefinition object. This should then be stored as a new key-value pair in the CanDatabase dictionary.
The issue is that path should specify a property path which for statically-typed objects would be...well, static (an exception to this is that it allows for referencing array elements e.g. /pwms/3 as above).
What I've tried
A. The Leeroy Jenkins approach
Forget the fact that I know it won't work - I tried the implementation below (which uses static-typing only despite the fact I need to support dynamic JSON Patch paths) just to see what happens.
Implementation
internal sealed class CanDatabaseModel : DeviceComponentModel<CanDatabaseModel>
{
public CanDatabaseModel()
{
this.Definitions = new Dictionary<string, CanMessageDefinition>();
}
[JsonProperty(PropertyName = "candb")]
public IDictionary<string, CanMessageDefinition> Definitions { get; }
...
}
Test
{
"op": "add",
"path": "/candb/foo",
"value": {
"messageId": 171,
"template": [17, 34],
"repeatRate": 100,
"canPort": 0
}
}
Outcome
An InvalidCastException is thrown at the site where I try to apply the specified changes to the JsonPatchDocument.
Site:
var currentModelSnapshot = this.currentModelFilter(this.currentModel.Copy());
var snapshotWithChangesApplied = currentModelSnapshot.Copy();
diffDocument.ApplyTo(snapshotWithChangesApplied);
Exception:
Unable to cast object of type 'Newtonsoft.Json.Serialization.JsonDictionaryContract' to type 'Newtonsoft.Json.Serialization.JsonObjectContract'.
B. Relying on dynamic JSON Patching
A more promising plan of attack seemed to be relying on dynamic JSON patching, which involves performing patch operations on instances of ExpandoObject. This allows you to use JSON patch documents to add, remove or replace properties since you're dealing with a dynamically-typed object.
Implementation
internal sealed class CanDatabaseModel : DeviceComponentModel<CanDatabaseModel>
{
public CanDatabaseModel()
{
this.Definitions = new ExpandoObject();
}
[JsonProperty(PropertyName = "candb")]
public IDictionary<string, object> Definitions { get; }
...
}
Test
{
"op": "add",
"path": "/candb/foo",
"value": {
"messageId": 171,
"template": [17, 34],
"repeatRate": 100,
"canPort": 0
}
}
Outcome
Making this change allows this part of my test to run without exceptions being raised, but JSON Patch has no knowledge of what to deserialise value as, resulting in the data being stored in the dictionary as a JObject rather than a CanMessageDefinition:
Would it be possible to 'tell' JSON Patch how to deserialise the information by any chance? Perhaps something along the lines of using a JsonConverter attribute on Definitions?
[JsonProperty(PropertyName = "candb")]
[JsonConverter(...)]
public IDictionary<string, object> Definitions { get; }
Summary
I need to support JSON patch requests that add values to a dictionary
I've tried going down the purely-static route, which failed
I've tried using dynamic JSON patching
This partly worked, but my data was stored as a JObject type instead of the intended type
Is there an attribute (or some other technique) I can apply to my property to let it deserialise to the correct type (not an anonymous type)?
Since there doesn't seem to be any official way to do it, I've come up with a Temporary Solution™ (read: a solution that works well enough so I'll probably keep it forever).
In order to make it seem like JSON Patch handles dictionary-like operations, I created a class called DynamicDeserialisationStore which inherits from DynamicObject and makes use of JSON Patch's support for dynamic objects.
More specifically, this class overrides methods like TrySetMember, TrySetIndex, TryGetMember, etc. to essentially act like a dictionary, except that it delegates all these operations to callbacks provided to its constructor.
Implementation
The code below provides the implementation of DynamicDeserialisationStore. It implements IDictionary<string, object> (which is the signature JSON Patch requires to work with dynamic objects) but I only implement the bare minimum of the methods I require.
The problem with JSON Patch's support for dynamic objects is that it will set properties to JObject instances i.e. it won't automatically perform deserialisation like it would when setting static properties, as it can't infer the type. DynamicDeserialisationStore is parameterised on the type of object that it will try to automatically try to deserialise these JObject instances to when they're set.
The class accepts callbacks to handle basic dictionary operations instead of maintaining an internal dictionary itself, because in my "real" system model code I don't actually use a dictionary (for various reasons) - I just make it appear that way to clients.
internal sealed class DynamicDeserialisationStore<T> : DynamicObject, IDictionary<string, object> where T : class
{
private readonly Action<string, T> storeValue;
private readonly Func<string, bool> removeValue;
private readonly Func<string, T> retrieveValue;
private readonly Func<IEnumerable<string>> retrieveKeys;
public DynamicDeserialisationStore(
Action<string, T> storeValue,
Func<string, bool> removeValue,
Func<string, T> retrieveValue,
Func<IEnumerable<string>> retrieveKeys)
{
this.storeValue = storeValue;
this.removeValue = removeValue;
this.retrieveValue = retrieveValue;
this.retrieveKeys = retrieveKeys;
}
public int Count
{
get
{
return this.retrieveKeys().Count();
}
}
private IReadOnlyDictionary<string, T> AsDict
{
get
{
return (from key in this.retrieveKeys()
let value = this.retrieveValue(key)
select new { key, value })
.ToDictionary(it => it.key, it => it.value);
}
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if (indexes.Length == 1 && indexes[0] is string && value is JObject)
{
return this.TryUpdateValue(indexes[0] as string, value);
}
return base.TrySetIndex(binder, indexes, value);
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
if (indexes.Length == 1 && indexes[0] is string)
{
try
{
result = this.retrieveValue(indexes[0] as string);
return true;
}
catch (KeyNotFoundException)
{
// Pass through.
}
}
return base.TryGetIndex(binder, indexes, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return this.TryUpdateValue(binder.Name, value);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
try
{
result = this.retrieveValue(binder.Name);
return true;
}
catch (KeyNotFoundException)
{
return base.TryGetMember(binder, out result);
}
}
private bool TryUpdateValue(string name, object value)
{
JObject jObject = value as JObject;
T tObject = value as T;
if (jObject != null)
{
this.storeValue(name, jObject.ToObject<T>());
return true;
}
else if (tObject != null)
{
this.storeValue(name, tObject);
return true;
}
return false;
}
object IDictionary<string, object>.this[string key]
{
get
{
return this.retrieveValue(key);
}
set
{
this.TryUpdateValue(key, value);
}
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return this.AsDict.ToDictionary(it => it.Key, it => it.Value as object).GetEnumerator();
}
public void Add(string key, object value)
{
this.TryUpdateValue(key, value);
}
public bool Remove(string key)
{
return this.removeValue(key);
}
#region Unused methods
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
ICollection<string> IDictionary<string, object>.Keys
{
get
{
throw new NotImplementedException();
}
}
ICollection<object> IDictionary<string, object>.Values
{
get
{
throw new NotImplementedException();
}
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
throw new NotImplementedException();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
bool IDictionary<string, object>.ContainsKey(string key)
{
throw new NotImplementedException();
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
bool IDictionary<string, object>.TryGetValue(string key, out object value)
{
throw new NotImplementedException();
}
#endregion
}
Tests
The tests for this class are provided below. I create a mock system model (see image) and perform various JSON Patch operations on it.
Here's the code:
public class DynamicDeserialisationStoreTests
{
private readonly FooSystemModel fooSystem;
public DynamicDeserialisationStoreTests()
{
this.fooSystem = new FooSystemModel();
}
[Fact]
public void Store_Should_Handle_Adding_Keyed_Model()
{
// GIVEN the foo system currently contains no foos.
this.fooSystem.Foos.ShouldBeEmpty();
// GIVEN a patch document to store a foo called "test".
var request = "{\"op\":\"add\",\"path\":\"/foos/test\",\"value\":{\"number\":3,\"bazzed\":true}}";
var operation = JsonConvert.DeserializeObject<Operation<FooSystemModel>>(request);
var patchDocument = new JsonPatchDocument<FooSystemModel>(
new[] { operation }.ToList(),
new CamelCasePropertyNamesContractResolver());
// WHEN we apply this patch document to the foo system model.
patchDocument.ApplyTo(this.fooSystem);
// THEN the system model should now contain a new foo called "test" with the expected properties.
this.fooSystem.Foos.ShouldHaveSingleItem();
FooModel foo = this.fooSystem.Foos["test"] as FooModel;
foo.Number.ShouldBe(3);
foo.IsBazzed.ShouldBeTrue();
}
[Fact]
public void Store_Should_Handle_Removing_Keyed_Model()
{
// GIVEN the foo system currently contains a foo.
var testFoo = new FooModel { Number = 3, IsBazzed = true };
this.fooSystem.Foos["test"] = testFoo;
// GIVEN a patch document to remove a foo called "test".
var request = "{\"op\":\"remove\",\"path\":\"/foos/test\"}";
var operation = JsonConvert.DeserializeObject<Operation<FooSystemModel>>(request);
var patchDocument = new JsonPatchDocument<FooSystemModel>(
new[] { operation }.ToList(),
new CamelCasePropertyNamesContractResolver());
// WHEN we apply this patch document to the foo system model.
patchDocument.ApplyTo(this.fooSystem);
// THEN the system model should be empty.
this.fooSystem.Foos.ShouldBeEmpty();
}
[Fact]
public void Store_Should_Handle_Modifying_Keyed_Model()
{
// GIVEN the foo system currently contains a foo.
var originalFoo = new FooModel { Number = 3, IsBazzed = true };
this.fooSystem.Foos["test"] = originalFoo;
// GIVEN a patch document to modify a foo called "test".
var request = "{\"op\":\"replace\",\"path\":\"/foos/test\", \"value\":{\"number\":6,\"bazzed\":false}}";
var operation = JsonConvert.DeserializeObject<Operation<FooSystemModel>>(request);
var patchDocument = new JsonPatchDocument<FooSystemModel>(
new[] { operation }.ToList(),
new CamelCasePropertyNamesContractResolver());
// WHEN we apply this patch document to the foo system model.
patchDocument.ApplyTo(this.fooSystem);
// THEN the system model should contain a modified "test" foo.
this.fooSystem.Foos.ShouldHaveSingleItem();
FooModel foo = this.fooSystem.Foos["test"] as FooModel;
foo.Number.ShouldBe(6);
foo.IsBazzed.ShouldBeFalse();
}
#region Mock Models
private class FooModel
{
[JsonProperty(PropertyName = "number")]
public int Number { get; set; }
[JsonProperty(PropertyName = "bazzed")]
public bool IsBazzed { get; set; }
}
private class FooSystemModel
{
private readonly IDictionary<string, FooModel> foos;
public FooSystemModel()
{
this.foos = new Dictionary<string, FooModel>();
this.Foos = new DynamicDeserialisationStore<FooModel>(
storeValue: (name, foo) => this.foos[name] = foo,
removeValue: name => this.foos.Remove(name),
retrieveValue: name => this.foos[name],
retrieveKeys: () => this.foos.Keys);
}
[JsonProperty(PropertyName = "foos")]
public IDictionary<string, object> Foos { get; }
}
#endregion
}
You could, for instance, deserialize your received Json into an object:
var dataDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
And iterate over it, casting and converting the values of the KeyValuePairs you want to patch into your destination type, CanMessageDefinition:
Dictionary<string, CanMessageDefinition> updateData = new Dictionary<string, CanMessageDefinition>();
foreach (var record in dataDict)
{
CanMessageDefinition recordValue = (CanMessageDefinition)record.Value;
if (yourExistingRecord.KeyAttributes.Keys.Contains(record.Key) && (!yourExistingRecord.KeyAttributes.Values.Equals(record.Value)))
{
updateData.Add(record.Key, recordValue);
}
}
And just save your object to your db.
An alternative would be to do this inside a JsonConverter as you mentioned. Cheers
I need to deserialize json for following class.
public class Test
{
public string Property { get; set; }
private Test()
{
//NOTHING TO INITIALIZE
}
public Test(string prop)
{
Property = prop;
}
}
I can create an instance of Test like
var instance = new Test("Instance");
considering my json something like
"{ "Property":"Instance" }"
How shall I create an object of Test class as my default constructor is private and I am getting object where Property is NULL
I am using Newtonsoft Json parser.
You can make Json.Net call the private constructor by marking it with a [JsonConstructor] attribute:
[JsonConstructor]
private Test()
{
//NOTHING TO INITIALIZE
}
Note that the serializer will still use the public setters to populate the object after calling the constructor.
Another possible option is to use the ConstructorHandling setting:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
Test t = JsonConvert.DeserializeObject<Test>(json, settings);
It doesn't seem like you need to take any extra steps.
Using Json.NET v6.0.8, the following C# program works inside LINQPad:
void Main()
{
var o = JsonConvert.DeserializeObject<Test>("{\"Property\":\"Instance\"}");
Debug.Assert(o.Property == "Instance",
"Property value not set when deserializing.");
}
public class Test
{
public string Property { get; set; }
private Test()
{
}
public Test(string propertyValue)
{
Property = propertyValue;
}
}
No need to create a Serializer setting and give assign ConstructorHandling here. Please remember to define the [JsonConstructor] attribute to the private constructor.
I have similar case with abstract BaseNode.cs and its concrete ComputerNode.cs implementation. You can create the classes, copy/paste the code below and do some experiment.
public abstract class BaseNode
{
[JsonConstructor] // ctor used when Json Deserializing
protected BaseNode(string Owner, string Name, string Identifier)
{
this.Name = Name;
this.Identifier = Identifier;
}
// ctor called by concrete class.
protected BaseNode(string [] specifications)
{
if (specifications == null)
{
throw new ArgumentNullException();
}
if (specifications.Length == 0)
{
throw new ArgumentException();
}
Name = specifications[0];
Identifier = specifications[1];
}
public string Name{ get; protected set; }
public string Identifier { get; protected set; }
}
public class ComputerNode: BaseNode
{
public string Owner { get; private set; }
[JsonConstructor] // not visible while creating object from outside and only used during Json Deserialization.
private ComputerNode(string Owner, string Name, string Identifier):base(Owner, Name, Identifier)
{
this.Owner = Owner;
}
public ComputerNode(string[] specifications):base(specifications)
{
Owner = specifications[2];
}
}
For JSon Read and Write following code helps -
public class Operation<T>
{
public string path;
public Operation()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "nodes.txt");
if (File.Exists(path) == false)
{
using (File.Create(path))
{
}
}
this.path = path;
}
public void Write(string path, List<T> nodes)
{
var ser = JsonConvert.SerializeObject(nodes, Formatting.Indented);
File.WriteAllText(path, ser);
}
public List<T> Read(string path)
{
var text = File.ReadAllText(path);
var res = JsonConvert.DeserializeObject<List<T>>(text);
return res;
}
}
All the best!
Today the short answer is: Rename the constructor parameter prop to property and your code will work fine.
public class Test
{
public string Property { get; }
public Test(string property)
{
Property = property;
}
}
Console.WriteLine(
JsonConvert.DeserializeObject(new Test("Instance")));
Newtonsoft.Json supports initializing properties using constructor parameters out of the box, without needing to set any additional attributes or changing any settings. The only constraint is that the parameter name needs to be a case insensitive match to the property name.
I discovered today that having a public constructor that takes parameters and no declared unparameterized constructor causes NewtonSoft to attempt to call the public constructor, the only one that it can find, since there is no explicit default constructor, and it cannot apparently find and call the default constructor provided by the framework unless it is the only constructor.
Explicitly declaring a default constructor causes NewtonSoft to call the correct (unparameterized) constructor.
I need to make all my entities serializable. So I was thinking in a BaseEntity with a Backup and a Restore method. But in the restore I can't override the object with the saved one because this is read-only.
Any solution or some other way to get the serializable entities?
My code:
internal class BaseEntity
{
private MemoryStream ms = new MemoryStream();
private BinaryFormatter bf = new BinaryFormatter();
public void Backup()
{
bf.Serialize(ms, this);
}
public void Restore()
{
this = (BaseEntity)bf.Deserialize(ms);
}
}
The more common pattern is to not make it the responsibility of your objects to serialize/deserialize themselves; rather, use an external serializer:
var serializer = new DataContractJsonSerializer(typeof(YourClass));
var stream = ...;
YourClass yourObj = ...;
serializer.WriteObject(stream, yourObj);
var restoredObj = serializer.ReadObject(stream);
Edit: One way serialization can work is to use the System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (or other implementation of IFormatter). To serialize an object you pass the object and a stream. To Deserialize the object, you pass a stream (positioned at the begining of your serialized data), and it returns the serialized object and all its depenedencies.
public static class EntityBackupServices
{
public static MemoryStream Backup (BaseEntity entity)
{
var ms = new MemoryStream();
Serialize (ms, entity);
ms.Position = 0;
return ms;
}
public static void Serialize (Stream stream, BaseEntity entity)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize (stream, entity);
}
public static BaseEntity Restore (Stream stream)
{
var binaryFormatter = new BinaryFormatter();
var entity = (BaseEntity) binaryFormatter.Deserialize (stream);
return entity;
}
}
One thing a formatter don't do (though the FormatterServices class makes it possible) is modify existing objects. So you probably don't want to have an instance method called Deserialize. You can't really do this: new LionEntity().Deserialize () where it replaces the fields of an existing instance.
Note: You'll need to put Serializable over all your types. Any fields that can't be serialized (because it's either not a struct, or it's not marked as [Serializable] will need to be marked with NonSerialized.
// A test object that needs to be serialized.
[Serializable()]
public class BaseEntity
{
public int member1;
public string member2;
public string member3;
public double member4;
// A field that is not serialized.
[NonSerialized()] public MyRuntimeType memberThatIsNotSerializable;
public TestSimpleObject()
{
member1 = 11;
member2 = "hello";
member3 = "hello";
member4 = 3.14159265;
memberThatIsNotSerializable = new Form ();
}
public MemoryStream Backup ()
{
return EntityBackupServices.Backup (this);
}
}
Edit:
The way I've mentioned is a rather standard and accepted way. If you want to venture into hackdom, you can deserialize the object the way I've mentioned, then use reflection to set each field on your existing object to the value of the deserialized object.
public class BaseEntity
{
void Restore(Stream stream)
{
object deserialized = EntityBackupServices.RestoreDeserialize(stream);//As listed above
if (deserialized.GetType () != this.GetType ())
throw new Exception();
foreach (FieldInfo fi in GetType().GetFields())
{
fi.SetValue(this, fi.GetValue (deserialized));
}
}
}
public IEntidadBase Restore()
{
return (IEntidadBase)bf.Deserialize(ms);
}
#jacklondon how would you do EntitySerializer methods?
You can do serialization process with http://www.servicestack.net/ StackService.Text module for clean entities. You don't need any attribute (serializable/datacontract) in ms way.
public class EntityFoo
{
public string Bar { get; set; }
public EntityFoo (string bar)
{
Bar = bar;
}
}
public class EntityDumper //and the EntitySerializer
{
public static string Dump<T> (T entity)
{
return new TypeSerializer<T> ().SerializeToString (entity);
}
public static T LoadBack<T> (string dump)
{
return new TypeSerializer<T> ().DeserializeFromString (dump);
}
}
public class dump_usage
{
public void start ()
{
string dump = EntityDumper.Dump (new EntityFoo ("Space"));
EntityFoo loaded = EntityDumper.LoadBack<EntityFoo> (dump);
Debug.Assert (loaded.Bar == "Space");
}
}
I don't necessarily recommend this, but here is one pattern for an object that can persist and restore its own state using serialization that creates new instances:
public sealed class MyClass
{
private Data _data = new Data();
//Properties go here (access the public fields on _data)
public void Backup()
{
//Serialize Data
}
public void Restore()
{
//Deserialize Data and set new instance
}
private sealed class Data
{
//Public fields go here (they're private externally [because Data is private], but public to MyClass.)
}
}
Note that this only works if your serializer supports non-public classes. Worst-case, you have to make the nested class public, which is ugly, but doesn't hurt encapsulation (since the instance is private).
I'm trying out Generics and I had this (not so) great idea of creating an XMLSerializer class. The code I pieced together is below:
public class Persist<T>
{
private string _path;
public Persist(string path) {
this._path = path;
}
public void save(T objectToSave)
{
XmlSerializer s = new XmlSerializer(typeof(T));
TextWriter w = new StreamWriter(this._path);
try { s.Serialize(w, objectToSave); }
catch (InvalidDataException e) { throw e; }
w.Close(); w.Dispose();
}
public T load()
{
XmlSerializer s = new XmlSerializer(typeof(T));
TextReader r = new StreamReader(this._path);
T obj;
try { obj = (T)s.Deserialize(r); }
catch (InvalidDataException e) { throw e; }
r.Close(); r.Dispose();
return obj;
}
}
Here's the problem: It works fine on Persist<List<string>> or Persist<List<int>> but not on Persist<List<userObject>> or any other custom (but serializable) objects. userObject itself is just a class with two {get;set;} properties, which I have serialized before.
I'm not sure if the problems on my Persist class (generics), XML Serialization code, or somewhere else :( Help is very much appreciated~
Edit:
code for userObject
public class userObject
{
public userObject(string id, string name)
{
this.id = id;
this.name = name;
}
public string id { get;private set; }
public string name { get;set; }
}
Looks to me like your code should just work - even though it does have a few flaws.
EDIT: Your userObject class isn't serializable. Xml serialization only works on types with a public, parameterless constructor - the current class won't work. Also, you should really rewrite your code to avoid explicit calls to .Close() or .Dispose() and instead prefer using where possible - as is, you might get random file locking if at any point during serialization an error occurs and your method terminates by exception - and thus doesn't call .Dispose().
Personally, I tend to use a just-for-serialization object hierarchy that's just a container for data stored in xml and avoids any behavior - particularly side effects. Then you can use a handly little base class that makes this simple.
What I use in my projects is the following:
public class XmlSerializableBase<T> where T : XmlSerializableBase<T>
{
static XmlSerializer serializer = new XmlSerializer(typeof(T));
public static T Deserialize(XmlReader from) { return (T)serializer.Deserialize(from); }
public void SerializeTo(Stream s) { serializer.Serialize(s, this); }
public void SerializeTo(TextWriter w) { serializer.Serialize(w, this); }
public void SerializeTo(XmlWriter xw) { serializer.Serialize(xw, this); }
}
...which caches the serializer in a static object, and simplifies usage (no generic type-paramenters needed at call-locations.
Real-life classes using it:
public class ArtistTopTracks {
public string name;
public string mbid;//always empty
public long reach;
public string url;
}
[XmlRoot("mostknowntracks")]
public class ApiArtistTopTracks : XmlSerializableBase<ApiArtistTopTracks> {
[XmlAttribute]
public string artist;
[XmlElement("track")]
public ArtistTopTracks[] track;
}
Sample serialization calls:
using (var xmlReader = XmlReader.Create([...]))
return ApiArtistTopTracks.Deserialize(xmlReader);
//[...]
ApiArtistTopTracks toptracks = [...];
toptracks.SerializeTo(Console.Out);
There can be a number of reasons why your code fails: This text is particularly helpful when having issues: Troubleshooting Common Problems with the XmlSerializer . Maybe you have some type hierarchy in your user objects and the serializer does not know about it?