/// <summary> /// Verify the current token is a required value, otherwise throw a good exception. /// </summary> /// <param name="reader">ITreeReader to check</param> /// <param name="expected">Current TreeToken expected</param> public static void Expect(this ITreeReader reader, TreeToken expected) { if (reader.TokenType != expected) { throw new IOException($"{reader.GetType().Name} expected \"{expected}\" but found \"{reader.TokenType}\" at {reader.Position:n0}"); } }
/// <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); }