예제 #1
0
        /// <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
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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);
        }
예제 #6
0
        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));
                }
            }
        }