1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-10 17:34:07 +02:00

Add classes for serializing objects to/from text files

This commit is contained in:
chylex 2017-07-07 00:48:00 +02:00
parent 71b306d5fd
commit 796fb348a3
6 changed files with 191 additions and 0 deletions

View File

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace TweetDuck.Data.Serialization{
class FileSerializer<T> where T : ISerializedObject{
private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n";
private static readonly ITypeConverter BasicSerializerObj = new BasicSerializer();
private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> serializers;
public FileSerializer(){
this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name);
this.serializers = new Dictionary<Type, ITypeConverter>();
}
public void RegisterSerializer(Type type, ITypeConverter serializer){
serializers[type] = serializer;
}
public void Write(string file, T obj){
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){
Type type = prop.Value.PropertyType;
object value = prop.Value.GetValue(obj);
if (!serializers.TryGetValue(type, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryWriteType(type, value, out string converted)){
if (converted != null){
writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}");
writer.Write(NewLineReal);
}
}
else{
throw new SerializationException($"Invalid serialization type, conversion failed for: {type}");
}
}
}
}
public void Read(string file, T obj){
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){
int space = line.IndexOf(' ');
if (space == -1){
throw new SerializationException($"Invalid file format, missing separator: {line}");
}
string property = line.Substring(0, space);
string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine);
if (props.TryGetValue(property, out PropertyInfo info)){
if (!serializers.TryGetValue(info.PropertyType, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryReadType(info.PropertyType, value, out object converted)){
info.SetValue(obj, converted);
}
else{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
}
}
else if (!obj.OnReadUnknownProperty(property, value)){
throw new SerializationException($"Invalid file format, unknown property: {property}+");
}
}
}
}
private class BasicSerializer : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
converted = value.ToString();
return true;
case TypeCode.Int32:
converted = ((int)value).ToString(); // cast required for enums
return true;
case TypeCode.String:
converted = value?.ToString();
return true;
default:
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
if (bool.TryParse(value, out bool b)){
converted = b;
return true;
}
else goto default;
case TypeCode.Int32:
if (int.TryParse(value, out int i)){
converted = i;
return true;
}
else goto default;
case TypeCode.String:
converted = value;
return true;
default:
converted = null;
return false;
}
}
}
}
}

View File

@ -0,0 +1,5 @@
namespace TweetDuck.Data.Serialization{
interface ISerializedObject{
bool OnReadUnknownProperty(string property, string value);
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace TweetDuck.Data.Serialization{
interface ITypeConverter{
bool TryWriteType(Type type, object value, out string converted);
bool TryReadType(Type type, string value, out object converted);
}
}

View File

@ -201,7 +201,10 @@
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ISerializedObject.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" />
<Compile Include="Data\TwoKeyDictionary.cs" />
<Compile Include="Data\WindowState.cs" />
<Compile Include="Core\Utils\WindowsUtils.cs" />

View File

@ -0,0 +1,45 @@
using System;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data.Serialization;
namespace UnitTests.Data{
[TestClass]
public class TestFileSerializer{
private class SerializationTestBasic : ISerializedObject{
public bool TestBool { get; set; }
public int TestInt { get; set; }
public string TestString { get; set; }
public string TestStringNull { get; set; }
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
}
[TestMethod]
public void TestBasicWriteRead(){
FileSerializer<SerializationTestBasic> serializer = new FileSerializer<SerializationTestBasic>();
SerializationTestBasic write = new SerializationTestBasic{
TestBool = true,
TestInt = -100,
TestString = "abc"+Environment.NewLine+"def",
TestStringNull = null
};
serializer.Write("serialized_basic", write);
Assert.IsTrue(File.Exists("serialized_basic"));
TestUtils.DeleteFileOnExit("serialized_basic");
SerializationTestBasic read = new SerializationTestBasic();
serializer.Read("serialized_basic", read);
Assert.IsTrue(read.TestBool);
Assert.AreEqual(-100, read.TestInt);
Assert.AreEqual("abc"+Environment.NewLine+"def", read.TestString);
Assert.IsNull(read.TestStringNull);
}
}
}

View File

@ -51,6 +51,7 @@
<Compile Include="Core\TestBrowserUtils.cs" />
<Compile Include="Data\TestCommandLineArgs.cs" />
<Compile Include="Core\TestCommandLineArgsParser.cs" />
<Compile Include="Data\TestFileSerializer.cs" />
<Compile Include="Data\TestInjectedHTML.cs" />
<Compile Include="Data\TestTwoKeyDictionary.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />