Inheriting Serialization using the Curiously Recurring Template Pattern - c#

I have a base class defined as follows:
public abstract class XMLBackedObject<T> where T: XMLBackedObject<T>
{
/// <summary>
/// Load the specified xml file and deserialize it.
/// </summary>
/// <param name='filePath'>
/// File path to load
/// </param>
public static T Load(string filePath)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using(FileStream stream = new FileStream(filePath, FileMode.Open))
{
return serializer.Deserialize(stream) as T;
}
}
/// <summary>
/// Save this instance to the specified file path
/// </summary>
/// <param name='filePath'>
/// File path to save to.
/// </param>
public void Save(string filePath)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using(FileStream stream = new FileStream(filePath, FileMode.Create))
{
serializer.Serialize(stream, this);
}
}
}
And classes inherit it as follows:
Config.cs:
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class Config : XMLBackedObject<Config>
{
public Config()
{
}
public string WordDirectoryPath;
public string CommandDirectoryPath;
}
Command.cs:
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public abstract class Command : XMLBackedObject<Command>
{
//The word that triggers this command
public Word Word;
//The command's target
public List<Word> Targets;
//Minimum number of targets for the command to be valid
public int RequiredTargets;
//Message to send when bad targets are supplied
public string BadTargetString;
//Message to send when no target is supplied
public string noTargetString;
public Command(Word word, List<Word> targets,int requiredTargets)
{
Targets = targets;
this.Word = word;
this.RequiredTargets = requiredTargets;
}
public Command()
{
Targets = new List<Word>();
}
/// <summary>
/// Execute the command on the supplied targets
/// </summary>
/// <param name='targets'>
/// Targets to process
/// </param>
public abstract void Execute(IEnumerable<Word> targets);
}
MenuNavigationCommand.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class MenuChoiceCommand : Command {
public MenuChoiceCommand()
{
}
public MenuChoiceCommand(Word word, List<Word> targets, int requiredTargets) : base(word,targets,requiredTargets)
{
}
public override void Execute (System.Collections.Generic.IEnumerable<Word> targets)
{
}
}
And this is the code that calls the Save functions:
public void BuildTestXMLFiles()
{
Config config = new Config();
config.CommandDirectoryPath = "commdirpath";
config.WordDirectoryPath = "wordirparth";
config.Save (Application.dataPath + "/testconfig.xml");
MenuChoiceCommand command = new MenuChoiceCommand(word,new List<Word>(),2);
command.Targets.Add (word);
command.Save (Application.dataPath + "/testcommand.xml");
}
Config's Save function executes without any hitches, but using Save on MenuNavigationCommand gives me this error:
InvalidOperationException: The type of the argument object 'MenuChoiceCommand' is not primitive.
All I need MenuNavigationCommand to do is save the fields that exist in the Command class it inherits from, not any new fields in MenuNavigationCommand. Is there any way to do this? Or should I just implement a Load and Save method on every class that uses more than one level of inheritance?
EDIT: Added the full source for the files.

MenuChoiceCommand inherits Command, which inherits XMLBackedObject<Command>, not XMLBackedObject<MenuChoiceCommand>. So the serializer created by Save is for type Command, not MenuChoiceCommand... You would need to make MenuChoiceCommand inherit XMLBackedObject<MenuChoiceCommand> for this to work (but then you wouldn't be able to make it inherit Command, since C# doesn't allow multiple inheritance).
Using the curiously recurring template pattern for this might seem a good idea at first glance, but as you can see, you can quickly encounter its limitations.
Anyway, I don't think the serialization logic should be part of the data class itself; it would probably be better to do it in a helper class with generic methods:
public static class XmlHelper
{
public static T Load<T>(string filePath)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using(FileStream stream = new FileStream(filePath, FileMode.Open))
{
return (T)serializer.Deserialize(stream);
}
}
public static void Save<T>(T obj, string filePath)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using(FileStream stream = new FileStream(filePath, FileMode.Create))
{
serializer.Serialize(stream, obj);
}
}
}

Related

Best way to unit test classes using streams?

I am currently writing some code to try to experiment with separating and abstracting two parts of our storage strategies at work. We currently use JSON format stored into a file and then retrieve it as our persistent storage. I am trying to experiment with separating the two concepts:
1) Concept one keeps the serialization separate from the storage type
2) Concept two keeps the storage type separate from the serialization strategy.
I found a good way that works doing some research on various threads, such as using TextWriter/TextReader instead of directly using Files so that any Stream type can be used (FileStream/MemoryStream/etc) so that the unit tests can be done without files. However, I am running into a problem since the TextWriter/TextReader classes which wrap the streams automatically close and dispose of the streams when they are themselves disposed, which is what I want in practice, but gets me stuck in unit testing.
Here is the code I have so far... this is for concept 1, the serialization process. Here are the interfaces for it:
/// <summary>
/// Interface for a serializer which reads from a stream and creates a type
/// </summary>
public interface IInSerializer
{
/// <summary>
/// Load type from a stream
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
bool Load(TextReader reader);
}
/// <summary>
/// Interface for writing a type out into a stream
/// </summary>
public interface IOutSerializer
{
/// <summary>
/// Save to the stream
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
bool Save(TextWriter writer);
}
/// <summary>
/// Helper interface which provides interface <see cref="IInSerializer"/>
/// and <see cref="IOutSerializer"/> for both reading/writing
/// </summary>
public interface IInOutSerializer : IInSerializer, IOutSerializer
{
}
Here is an abstract implementation of the serializer for JSON format:
/// <summary>
/// Implementation of <see cref="IInOutSerializer"/> which serializes into JSON format
/// </summary>
/// <typeparam name="T">Type to be serialized</typeparam>
public abstract class JSONSerializer<T> : IInOutSerializer
{
/// <summary>
/// Source of serialization
/// </summary>
public T Source { get; set; }
/// <summary>
/// Provided by very specific type to load the Jobject into type T
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool LoadJObject(JObject jObject);
/// <summary>
/// Provided by very specific type to save type T into a Jobject
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool Serialize(JObject jObject);
/// <summary>
/// <see cref="IInOutSerializer.Load"/>
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
public bool Load(TextReader reader)
{
using (var json = new JsonTextReader(reader))
{
var jObject = JToken.ReadFrom(json) as JObject;
if (jObject != null)
return LoadJObject(jObject);
}
return false;
}
/// <summary>
/// <see cref="IInOutSerializer.Save"/>
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
public bool Save(TextWriter writer)
{
var jObject = new JObject();
if (Serialize(jObject))
{
using (var json = new JsonTextWriter(writer))
{
json.Formatting = Formatting.Indented;
jObject.WriteTo(json);
return true;
}
}
return false;
}
}
And here is one of the concrete types for serializing my class MetroLineDetails:
public class MetroLineJSONSerializationStrategy : JSONSerializer<MetroLineDetails>
{
private class MetroLineHelper : IMetroLine, IMetroLineWritable
{
public string DestinationStation
{
get;
set;
}
public Color LineColor
{
get;
set;
}
public char LineLetter
{
get;
set;
}
public string Name
{
get;
set;
}
public bool SaturdayService
{
get;
set;
}
public string SourceStation
{
get;
set;
}
public bool SundayHolidayService
{
get;
set;
}
public static explicit operator MetroLineDetails(MetroLineHelper source)
{
return new MetroLineDetails(source.Name, source.LineColor, source.SourceStation, source.DestinationStation, source.SaturdayService, source.SundayHolidayService);
}
}
protected override bool LoadJObject(JObject jObject)
{
var helper = new MetroLineHelper();
jObject.Read(nameof(MetroLineDetails.Name), (t) => (string)t, (v) => helper.Name = v);
jObject.Read(nameof(MetroLineDetails.LineLetter), (t) => (char)t, (v) => helper.LineLetter = v);
jObject.Read(nameof(MetroLineDetails.SourceStation), (t) => (string)t, (v) => helper.SourceStation = v);
jObject.Read(nameof(MetroLineDetails.DestinationStation), (t) => (string)t, (v) => helper.DestinationStation = v);
jObject.Read(nameof(MetroLineDetails.SaturdayService), (t) => (bool)t, (v) => helper.SaturdayService = v);
jObject.Read(nameof(MetroLineDetails.SundayHolidayService), (t) => (bool)t, (v) => helper.SundayHolidayService = v);
var color = jObject.Read(nameof(MetroLineDetails.LineColor), (t) => (JObject)t);
helper.LineColor = color.ToColor();
Source = (MetroLineDetails)helper;
return true;
}
protected override bool Serialize(JObject jObject)
{
jObject.Add(nameof(MetroLineDetails.Name), Source.Name);
jObject.Add(nameof(MetroLineDetails.LineLetter), Source.LineLetter);
jObject.Add(nameof(MetroLineDetails.SourceStation), Source.SourceStation);
jObject.Add(nameof(MetroLineDetails.DestinationStation), Source.DestinationStation);
jObject.Add(nameof(MetroLineDetails.SaturdayService), Source.SaturdayService);
jObject.Add(nameof(MetroLineDetails.SundayHolidayService), Source.SundayHolidayService);
jObject.Add(nameof(MetroLineDetails.LineColor), Source.LineColor.ToJObject());
return true;
}
}
And now here are my storage type interfaces:
/// <summary>
/// Interface for the storage medium
/// </summary>
public interface IStorageMedium
{
/// <summary>
/// Save the information in the serializer
/// </summary>
/// <param name="serializer"></param>
void Save(IOutSerializer serializer);
/// <summary>
/// Load the information to the serializer
/// </summary>
/// <param name="serializer"></param>
void Load(IInSerializer serializer);
}
And the type specifically for files:
/// <summary>
/// Implementation of <see cref="IStorageMedium"/> which stores into a file
/// </summary>
public class FileStorageMedium : IStorageMedium
{
private readonly string _fileName;
public FileStorageMedium(string fileName)
{
_fileName = fileName;
}
public void Save(IOutSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Truncate))
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
}
}
public void Load(IInSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Open))
{
using (var reader = new StreamReader(stream))
{
serializer.Load(reader);
}
}
}
}
As you can see in each layer I want to follow best practices and make sure each method closes and flushes the stream for the caller and not leave things open for the sake of unit testing (I know I could probably change the code to not close the streams, but I don't think that is appropriate).
So, now, using the ideas I've found on the forums to not have anything tied specifically to file streams to help with unit testing, I'm still running into problems finding the best way to unit test this. Here is the unit test I am trying to write:
[TestClass]
public class MetroLine
{
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("A", Colors.Blue, "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
serializer.Save(writer);
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream))
{
var text = reader.ReadToEnd();
}
}
}
}
The stream is closed no matter what I do in the serializer.Save() call since that method uses a disposable which closes the stream (as I believe it should to prevent leaks). The problem is, I can no longer unit test the stream in any way to test whether any of this works. I get exceptions thrown saying you cannot access closed streams anymore, which makes sense. But how can I test the contents of my stream in any meaningful way?
I found GetBuffer on the MemoryStream which allows me to convert the raw buffer into a string and I can unit test the actual JSON blob however I want... here is what I wrote:
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("Inland Empire Line", Colors.Blue, 'A', "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
var bytes = stream.GetBuffer();
var json = System.Text.Encoding.UTF8.GetString(bytes);
Assert.AreEqual('{', json[0]);
}
}
My hopes are someone will find this useful!

cannot convert viewModel and ObservableCollection

In my follow second line, I found a convert error argument:
UserStackVM _listeStack = JsonWorker.ReadData();
ListeStacks = new ObservableCollection<UserStackVM>(_listeStack); // here
My error is:
cannot convert from 'MyStack.ViewModels.UserStackVM' to
'System.Collections.Generic.List'
UserStackVM is a ViewModel:
#region Properties
private string name;
...
private string[] path;
...
#endregion
JsonWorker is a static class using Json.NET (http://www.newtonsoft.com/json):
#region Properties
private static string _json;
private static UserStackVM _userStack;
#endregion
#region Methods
/// <summary>
/// Open the json config file. Create it if he doesn't exist.
/// </summary>
private static void OpenFile()
{
using (var stream = new FileStream(#"config.json", FileMode.OpenOrCreate))
{
using (var reader = new StreamReader(stream))
{
_json = reader.ReadToEnd();
}
}
}
/// <summary>
/// Read the json config file and return all data.
/// </summary>
/// <returns></returns>
public static UserStackVM ReadData()
{
OpenFile();
_userStack = JsonConvert.DeserializeObject<UserStackVM>(_json);
return _userStack;
}
#endregion
Per advance, thanks for your help.
'MyStack.ViewModels.UserStackVM' to 'System.Collections.Generic.List'
That ObservableCollection(T) Constructor expects a List<T> (list of instances); you are only providing a single instance. Change it to
UserStackVM _listeStack = JsonWorker.ReadData();
ListeStacks = new ObservableCollection<UserStackVM>();
ListeStacks.Add( _listeStack );
or
ListeStacks = new ObservableCollection<UserStackVM>
( new List<UserStackVM> () { listeStack } );

Only first object in list being deserialized

I'm having trouble with deserializing a list of objects. For some reason only the first object in the serialized list is returned from the load method. I've looked at numerous questions that seem related, but haven't been able to find a solution to my specific problem. I think it may have something to do with the private backing collection I'm using? Any help would be appreciated. (class names and methods have been made generic)
Here's my deserialization method:
public static ObjectList LoadLibrary()
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream loadStream;
string path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "ObjectList.bin");
ObjectList lib = null;
if (!File.Exists(path))
{
lib = new ObjectList();
return lib;
}
try
{
using (loadStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
lib = (ObjectList)formatter.Deserialize(loadStream);
}
}
catch (Exception e)
{
MessageBox.Show("Object list could not be loaded. Error: \n" + e);
}
return lib;
}
Here's my ObjectList class;
[Serializable]
public class ObjectList: IEnumerable<Obj>
{
#region Construction
/// <summary>
/// Contracts Test Case Library Constructor
/// </summary>
public ObjectList() { }
/// <summary>
/// Private backing collection
/// </summary>
private List<Obj> _objectList = new List<Obj>();
#endregion
And for good measure, here's my Obj class:
[Serializable]
public class Obj
{
#region Constructors
public Obj(string Name, string Description)
{
this.Name = Name;
this.Description = Description;
}
public Obj() { }
#endregion

Example: Mono's DataContractJsonSerializer won't serialize, but .NET will

This is a program that is ready to run in Xamarin and VS2013.
I'm having an issue where mono isn't calling the serializer Subclass, and need to work around that issue.
Question
How should I modify SetMembershipProof so that it will call a method with the attribute [OnSerializing] located in a nested subclass of ?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace MonoBug
{
class Program
{
static void Main(string[] args)
{
SetMembershipProof2 setMembershipProof = new SetMembershipProof2();
string setProofJSON = CryptoSerializer.Serialize<SetMembershipProof2>(setMembershipProof);
// Inspect the contents of SetProofJSON, it is null under mono, and not null in .NET
}
}
public class CryptoSerializer
{
/// <summary>
/// Serialize serializable types in namespace UProveCrypto.PolyProof.
/// </summary>
/// <typeparam name="T">input type</typeparam>
/// <param name="obj">instance of serializable type</param>
/// <returns>JSON string</returns>
public static string Serialize<T>(T obj)
{
string result;
try
{
using (MemoryStream ms = new MemoryStream())
{
DataContractJsonSerializer jsonSerializer =
new DataContractJsonSerializer(obj.GetType());
jsonSerializer.WriteObject(ms, obj);
ms.Position = 0;
StreamReader reader = new StreamReader(ms);
result = reader.ReadToEnd();
}
}
catch (Exception e)
{
throw new SerializationException(obj.GetType().Name, e);
}
return result;
}
}
[DataContract]
public abstract class GroupParameterizedSerializer2
{
[OnSerializing]
public void SerializeGroup(StreamingContext context)
{
}
}
[DataContract]
public class SetMembershipProof2 : GroupParameterizedSerializer2
{
#region Serialization
/// <summary>
/// Serialization of a
/// </summary>
[DataMember(Name = "a", EmitDefaultValue = false, Order = 2)]
internal string[] _a;
/// <summary>
/// Serialization of c
/// </summary>
[DataMember(Name = "c", EmitDefaultValue = false, Order = 3)]
internal string[] _c;
/// <summary>
/// Serialization of r
/// </summary>
[DataMember(Name = "r", EmitDefaultValue = false, Order = 4)]
internal string[] _r;
/// <summary>
/// Serialize a, c, r.
/// </summary>
/// <param name="context">The streaming context.</param>
[OnSerializing]
internal void OnSerializing(StreamingContext context)
{
Console.WriteLine("Debug: This isn't called in Mono...");
List<string> t = new List<string>();
t.Add("data1");
_a = t.ToArray();
t.Clear();
t.Add("data2");
_c = t.ToArray();
t.Clear();
t.Add("data3");
_r = t.ToArray();
}
#endregion
}
}
Change your serializer to:
internal string GetJson<T>(T obj)
{
...
DataContractJsonSerializer jsonSerializer =
new DataContractJsonSerializer(obj.GetType());
...
}
Depending on with how you call GetJson<T>, typeof(T) might return the superclass of the serialized object obj. On the other hand, obj.GetType() always returns the specific class of the object that is being serialized. This may be why the [OnSerializing] methods of the subclasses are not being called in Mono.

should this be mocked or stubbed?

I have the following method to test and i have written two tests, testing the scenario that an exception is thrown, and im wondering which is correct.
namespace JimBob.CsvImporter.Entity
{
public interface IIOManager
{
Stream OpenFile(string path);
TextReader ReturnReader(string path);
}
public class IOManager : IIOManager
{
public Stream OpenFile(string path)
{
return File.Open(path, FileMode.Open);
}
public TextReader ReturnReader(string filePath)
{
return new StreamReader(filePath);
}
}
public class EntityVerification
{
private IIOManager _iomgr;
public EntityVerification(IIOManager ioManager)
{
this._iomgr = ioManager;
}
...
/// <summary>
/// Ensures user can open file.
/// </summary>
/// <param name="errorMessageList">A running list of all errors encountered.</param>
public void ValidateAccessToFile(string filePath, List<string> errorMessageList)
{
try
{
using (FileStream fs = (FileStream)_iomgr.OpenFile(filePath))
{
if (fs.CanRead && fs.CanWrite) { }
else
{
errorMessageList.Add("Can not read/write to the specified file.");
}
}
}
catch (Exception e)
{
errorMessageList.Add(e.Message);
}
}
Tests:
[Test]
public void ValidateAccessToFile_CanReadWriteToFile_ThrowException()
{
List<String> errorMessageList = new List<string>();
StubService stub = new StubService();
EntityVerification testObject = new EntityVerification(stub);
testObject.ValidateAccessToFile("ergesrg", errorMessageList);
Assert.AreEqual(errorMessageList.Count, 0);
}
[Test]
public void ValidateAccessToFile_CanReadWriteToFile_ThrowsException()
{
Mock<IIOManager> mock = new Mock<IIOManager>();
mock.Setup(x => x.ReturnReader(It.IsAny<string>())).Throws(new InvalidOperation("throw baby."));
EntityVerification testObject = new EntityVerification(mock.Object);
List<String> errorMessageList = new List<string>();
testObject.ValidateAccessToFile("blabla.txt", errorMessageList);
Assert.AreEqual(errorMessageList.Count, 0);
}
public class StubService : IIOManager
{
public Exception ex;
public Stream OpenFile(String path)
{
throw ex;
}
}
Both tests just check that a local variable, in this case errorMessageList, to the test contains something so im not sure which i should be using.
Any comments would be appreciated.
Thanks
First, shouldn't you be checking that you do add an error message to the list?
Assert.AreEqual(errorMessageList.Count, 1);
Second, although the second one is a little less verbose, and more readable (as you don't need to implement another class), it doesn't matter - the two tests are both valid ways of achieving the same target. Just choose one and move on to your next feature...
Second test looks better. I think you'll have other methods with IIOManager you should maintain 1st test (update stub), but do nothing on the second one.
About IOManager, FileSystem - looks more suitable class name

Categories