/// <summary> /// ReadObject wraps the loop to read each property in an object and call the corresponding /// setter to set it. /// </summary> /// <remarks> /// This works for classes only, as the instance can't be passed by ref. /// Structs can serialize as arrays or directly implement the loop to decode themselves. /// Setters take a reference to the instance so that the Dictionary can be static per type, /// which is critical for acceptable performance. /// </remarks> /// <typeparam name="T">Type being deserialized</typeparam> /// <param name="reader">ITreeReader being read from</param> /// <param name="instance">T instance being initialized</param> /// <param name="setters">Dictionary of setter per field name</param> /// <param name="throwOnUnknown">Throw if property name not in setters found</param> public static void ReadObject <T>(this ITreeReader reader, T instance, Dictionary <string, Setter <T> > setters, bool throwOnUnknown = true) where T : ITreeSerializable { // Ensure object state reset before Read instance.Clear(); reader.Expect(TreeToken.StartObject); reader.Read(); while (reader.TokenType == TreeToken.PropertyName) { string propertyName = reader.ReadAsString(); reader.Read(); if (setters.TryGetValue(propertyName, out Setter <T> setter)) { setter(reader, instance); reader.Read(); } else { if (throwOnUnknown) { throw new IOException($"Found unknown {typeof(T).Name} property, \"{propertyName}\", expected one of \"{String.Join("; ", setters.Keys)}\" at {reader.Position:n0} using {reader.GetType().Name}."); } else { reader.Skip(); } } } reader.Expect(TreeToken.EndObject); // EndObject must be left for caller to handle }
/// <summary> /// Read existing items in an existing Dictionary instance. /// Used with Dictionaries of specific things which may or may not be present in the file, like Table.Columns. /// </summary> /// <typeparam name="T">Type of values in Dictionary</typeparam> /// <param name="reader">ITreeReader to read from</param> /// <param name="dictionary">Dictionary containing items to read</param> /// <param name="throwOnUnknown">True to throw for property name not in Dictionary, false to quietly skip over it</param> public static void ReadDictionaryItems <T>(this ITreeReader reader, Dictionary <string, T> dictionary, bool throwOnUnknown = true) where T : ITreeSerializable { reader.Expect(TreeToken.StartObject); reader.Read(); while (reader.TokenType == TreeToken.PropertyName) { string itemName = reader.ReadAsString(); reader.Read(); if (dictionary.TryGetValue(itemName, out T item)) { item.Read(reader); reader.Read(); } else { if (throwOnUnknown) { throw new IOException($"Found unknown {typeof(T).Name} property \"{itemName}\", expected one of \"{String.Join("; ", dictionary.Keys)}\" at {reader.Position:n0} using {reader.GetType().Name}."); } else { reader.Skip(); } } } reader.Expect(TreeToken.EndObject); }
public static Dictionary <int, T> ReadIntDictionary <T>(this ITreeReader reader, Func <T> ctor) where T : ITreeSerializable { if (reader.TokenType == TreeToken.Null) { return(null); } Dictionary <int, T> result = new Dictionary <int, T>(); reader.Expect(TreeToken.StartArray); reader.Read(); int[] keys = reader.ReadBlockArray <int>(); reader.Read(); reader.Expect(TreeToken.StartArray); reader.Read(); for (int i = 0; i < keys.Length; ++i) { int key = keys[i]; T value = ctor(); value.Read(reader); result[key] = value; reader.Read(); } reader.Expect(TreeToken.EndArray); reader.Read(); reader.Expect(TreeToken.EndArray); return(result); }
public static Dictionary <string, T> ReadStringDictionary <T>(this ITreeReader reader, Func <T> ctor) where T : ITreeSerializable { if (reader.TokenType == TreeToken.Null) { return(null); } Dictionary <string, T> result = new Dictionary <string, T>(); reader.Expect(TreeToken.StartObject); reader.Read(); while (reader.TokenType == TreeToken.PropertyName) { string key = reader.ReadAsString(); reader.Read(); T value = ctor(); value.Read(reader); result[key] = value; reader.Read(); } reader.Expect(TreeToken.EndObject); return(result); }
public static List <T> ReadList <T>(this ITreeReader reader, Func <T> ctor) where T : ITreeSerializable { if (reader.TokenType == TreeToken.Null) { return(null); } List <T> result = new List <T>(); reader.Expect(TreeToken.StartArray); reader.Read(); while (reader.TokenType != TreeToken.EndArray) { T item = ctor(); item.Read(reader); result.Add(item); reader.Read(); } return(result); }
public static void Basics(TreeFormat format) { Random r = new Random(); // Test integers with specific values (for varying length encodings) TestIntegers(format); // Test serialization of each primitive value type (bool, string, long, double) Sample sample = new Sample(new Random()); sample.AssertEqual(RoundTrip <Sample>(sample, format)); // Test serialization of containers (read must leave last token of nested items so loop finds next property name properly) SingleContainer <Sample> container = new SingleContainer <Sample>(sample); container.AssertEqual(RoundTrip <SingleContainer <Sample> >(container, format)); // Test diagnostics doesn't throw when over Reader TreeDiagnostics diagnostics = Diagnostics(sample, format); // Test serialization of all supported primitive array types RoundTripArray(new byte[] { 0, 4, 16 }, format); RoundTripArray(new char[] { 'S', 'o', 'A' }, format); RoundTripArray(new sbyte[] { 0, 4, 16 }, format); RoundTripArray(new ushort[] { 0, 4, 16 }, format); RoundTripArray(new short[] { 0, 4, 16 }, format); RoundTripArray(new uint[] { 0, 4, 16 }, format); RoundTripArray(new int[] { 0, 4, 16 }, format); RoundTripArray(new ulong[] { 0, 4, 16 }, format); RoundTripArray(new long[] { 0, 4, 16 }, format); RoundTripArray(new float[] { 0.0f, 100.5f, -2.05f }, format); RoundTripArray(new double[] { 0.0f, 100.5f, -2.05f }, format); // Verify exception on (expected) unsupported type Assert.Throws <NotSupportedException>(() => RoundTripArray(new decimal[] { 0.01M, 0.02M }, format)); // Null/Empty array handling (currently expected to come back as empty array) RoundTripArray <byte>(null, format); RoundTripArray <byte>(new byte[] { }, format); // Test double Dispose handled correctly, 'Verbose == true' works, 'LeaveStreamOpen' respected container.AssertEqual(RoundTrip(container, format, testDoubleDispose: true)); container.AssertEqual(RoundTrip(container, format, new TreeSerializationSettings() { LeaveStreamOpen = true })); container.AssertEqual(RoundTrip(container, format, new TreeSerializationSettings() { Verbose = true })); // Test null string handling sample.Name = null; sample.AssertEqual(RoundTrip(sample, format)); // Test settings defaulting sample.AssertEqual(RoundTrip_NullSettings(sample, format)); // Test Skip behavior works for each primitive value VerifySkip(sample, format); // Test serialization details using (MemoryStream stream = new MemoryStream()) { TreeSerializationSettings settings = new TreeSerializationSettings() { LeaveStreamOpen = true }; using (ITreeWriter writer = Writer(format, stream, settings)) { sample.Write(writer); } long bytesWritten = stream.Position; stream.Seek(0, SeekOrigin.Begin); using (ITreeReader reader = Reader(format, stream, settings)) { // Test 'Expect' throwing (should not be 'None' when file just opened) Assert.NotEqual(TreeToken.None, reader.TokenType); Assert.Throws <IOException>(() => reader.Expect(TreeToken.None)); // Test reading back as wrong type (exception from ReadObject unexpected property name) Assert.Throws <IOException>(() => new SingleContainer <Sample>().Read(reader)); } } }