/// <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); }
/// <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 }
public static void VerifySkip <T>(T value, TreeFormat format) where T : ITreeSerializable { // Test serialization details using (MemoryStream stream = new MemoryStream()) { TreeSerializationSettings settings = new TreeSerializationSettings() { LeaveStreamOpen = true }; using (ITreeWriter writer = Writer(format, stream, settings)) { value.Write(writer); } long bytesWritten = stream.Position; // Read tokens individually and verify 'None' returned at end stream.Seek(0, SeekOrigin.Begin); using (ITreeReader reader = Reader(format, stream, settings)) { while (reader.Read()) { // Verify each token type is coming back properly (no reading random bytes) Assert.True((byte)reader.TokenType <= (byte)TreeToken.BlockArray); } Assert.Equal(TreeToken.None, reader.TokenType); Assert.Equal(bytesWritten, stream.Position); } // Verify Skip once skips everything (each ITreeSerializable must be one value or one root array or object stream.Seek(0, SeekOrigin.Begin); using (ITreeReader reader = Reader(format, stream, settings)) { reader.Skip(); Assert.Equal(TreeToken.None, reader.TokenType); Assert.Equal(bytesWritten, stream.Position); } // For objects, verify each property can be skipped correctly // Each Skip should read the value, so that the next token is the next PropertyName stream.Seek(0, SeekOrigin.Begin); using (ITreeReader reader = Reader(format, stream, settings)) { if (reader.TokenType == TreeToken.StartObject) { Empty empty = new Empty(); empty.Read(reader); Assert.Equal(bytesWritten, stream.Position); } } } }