diff --git a/Helpers/Updater/JsonParser.cs b/Helpers/Updater/JsonParser.cs index 17848e6..5637f63 100644 --- a/Helpers/Updater/JsonParser.cs +++ b/Helpers/Updater/JsonParser.cs @@ -1,480 +1,497 @@ -// -// Copyright (c) PlaceholderCompany. All rights reserved. -// - -namespace SystemTrayMenu.Helpers.Updater -{ - // Copyright (c) 2018 Alex Parker - // Copyright (c) 2018-2019 Peter Kirmeier - - // Permission is hereby granted, free of charge, to any person obtaining a copy of - // this software and associated documentation files (the "Software"), to deal in - // the Software without restriction, including without limitation the rights to - // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - // the Software, and to permit persons to whom the Software is furnished to do so, - // subject to the following conditions: - - // The above copyright notice and this permission notice shall be included in all - // copies or substantial portions of the Software. - - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - using System; - using System.Collections; - using System.Collections.Generic; - using System.Reflection; - using System.Runtime.Serialization; - using System.Text; - - // Really simple JSON parser in ~300 lines - // - Attempts to parse JSON files with minimal GC allocation - // - Nice and simple "[1,2,3]".FromJson>() API - // - Classes and structs can be parsed too! - // class Foo { public int Value; } - // "{\"Value\":10}".FromJson() - // - Can parse JSON without type information into Dictionary and List e.g. - // "[1,2,3]".FromJson().GetType() == typeof(List) - // "{\"Value\":10}".FromJson().GetType() == typeof(Dictionary) - // - No JIT Emit support to support AOT compilation on iOS - // - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead. - // - Only public fields and property setters on classes/structs will be written to - // - // Limitations: - // - No JIT Emit support to parse structures quickly - // - Limited to parsing <2GB JSON files (due to int.MaxValue) - // - Parsing of abstract classes or interfaces is NOT supported and will throw an exception. - public static class JSONParser - { - [ThreadStatic] - private static Stack> splitArrayPool; - [ThreadStatic] - private static StringBuilder stringBuilder; - [ThreadStatic] - private static Dictionary> fieldInfoCache; - [ThreadStatic] - private static Dictionary> propertyInfoCache; - - public static T FromJson(this string json) - { - // Initialize, if needed, the ThreadStatic variables - propertyInfoCache ??= new Dictionary>(); - - fieldInfoCache ??= new Dictionary>(); - - stringBuilder ??= new StringBuilder(); - - splitArrayPool ??= new Stack>(); - - // Remove all whitespace not within strings to make parsing simpler - stringBuilder.Length = 0; - for (int i = 0; i < json.Length; i++) - { - char c = json[i]; - if (c == '"') - { - i = AppendUntilStringEnd(true, i, json); - continue; - } - - if (char.IsWhiteSpace(c)) - { - continue; - } - - stringBuilder.Append(c); - } - - // Parse the thing! - return (T)ParseValue(typeof(T), stringBuilder.ToString()); - } - - internal static object ParseValue(Type type, string json) - { - if (type == typeof(string)) - { - if (json.Length <= 2) - { - return string.Empty; - } - - StringBuilder parseStringBuilder = new(json.Length); - for (int i = 1; i < json.Length - 1; ++i) - { - if (json[i] == '\\' && i + 1 < json.Length - 1) - { - int j = "\"\\nrtbf/".IndexOf(json[i + 1]); - if (j >= 0) - { - parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]); - ++i; - continue; - } - - if (json[i + 1] == 'u' && i + 5 < json.Length - 1) - { - if (uint.TryParse(json.AsSpan(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c)) - { - parseStringBuilder.Append((char)c); - i += 5; - continue; - } - } - } - - parseStringBuilder.Append(json[i]); - } - - return parseStringBuilder.ToString(); - } - - if (type.IsPrimitive) - { - var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture); - return result; - } - - if (type == typeof(decimal)) - { - decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result); - return result; - } - - if (json == "null") - { - return null; - } - - if (type.IsEnum) - { - if (json[0] == '"') - { - json = json[1..^1]; - } - - try - { - return Enum.Parse(type, json, false); - } - catch - { - return 0; - } - } - - if (type.IsArray) - { - Type arrayType = type.GetElementType(); - if (json[0] != '[' || json[^1] != ']') - { - return null; - } - - List elems = Split(json); - Array newArray = Array.CreateInstance(arrayType, elems.Count); - for (int i = 0; i < elems.Count; i++) - { - newArray.SetValue(ParseValue(arrayType, elems[i]), i); - } - - splitArrayPool.Push(elems); - return newArray; - } - - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) - { - Type listType = type.GetGenericArguments()[0]; - if (json[0] != '[' || json[^1] != ']') - { - return null; - } - - List elems = Split(json); - var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count }); - for (int i = 0; i < elems.Count; i++) - { - list.Add(ParseValue(listType, elems[i])); - } - - splitArrayPool.Push(elems); - return list; - } - - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - Type keyType, valueType; - { - Type[] args = type.GetGenericArguments(); - keyType = args[0]; - valueType = args[1]; - } - - // Refuse to parse dictionary keys that aren't of type string - if (keyType != typeof(string)) - { - return null; - } - - // Must be a valid dictionary element - if (json[0] != '{' || json[^1] != '}') - { - return null; - } - - // The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return null; - } - - var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 }); - for (int i = 0; i < elems.Count; i += 2) - { - if (elems[i].Length <= 2) - { - continue; - } - - string keyValue = elems[i][1..^1]; - object val = ParseValue(valueType, elems[i + 1]); - dictionary.Add(keyValue, val); - } - - return dictionary; - } - - if (type == typeof(object)) - { - return ParseAnonymousValue(json); - } - - if (json[0] == '{' && json[^1] == '}') - { - return ParseObject(type, json); - } - - return null; - } - - private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json) - { - stringBuilder.Append(json[startIdx]); - for (int i = startIdx + 1; i < json.Length; i++) - { - if (json[i] == '\\') - { - if (appendEscapeCharacter) - { - stringBuilder.Append(json[i]); - } - - stringBuilder.Append(json[i + 1]); - i++; // Skip next character as it is escaped - } - else if (json[i] == '"') - { - stringBuilder.Append(json[i]); - return i; - } - else - { - stringBuilder.Append(json[i]); - } - } - - return json.Length - 1; - } - - // Splits { :, : } and [ , ] into a list of strings - private static List Split(string json) - { - List splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List(); - splitArray.Clear(); - if (json.Length == 2) - { - return splitArray; - } - - int parseDepth = 0; - stringBuilder.Length = 0; - for (int i = 1; i < json.Length - 1; i++) - { - switch (json[i]) - { - case '[': - case '{': - parseDepth++; - break; - case ']': - case '}': - parseDepth--; - break; - case '"': - i = AppendUntilStringEnd(true, i, json); - continue; - case ',': - case ':': - if (parseDepth == 0) - { - splitArray.Add(stringBuilder.ToString()); - stringBuilder.Length = 0; - continue; - } - - break; - } - - stringBuilder.Append(json[i]); - } - - splitArray.Add(stringBuilder.ToString()); - - return splitArray; - } - - private static object ParseAnonymousValue(string json) - { - if (json.Length == 0) - { - return null; - } - - if (json[0] == '{' && json[^1] == '}') - { - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return null; - } - - var dict = new Dictionary(elems.Count / 2); - for (int i = 0; i < elems.Count; i += 2) - { - dict.Add(elems[i][1..^1], ParseAnonymousValue(elems[i + 1])); - } - - return dict; - } - - if (json[0] == '[' && json[^1] == ']') - { - List items = Split(json); - var finalList = new List(items.Count); - for (int i = 0; i < items.Count; i++) - { - finalList.Add(ParseAnonymousValue(items[i])); - } - - return finalList; - } - - if (json[0] == '"' && json[^1] == '"') - { - return ParseValue(typeof(string), json); // fix https://github.com/zanders3/json/issues/29 - } - - if (char.IsDigit(json[0]) || json[0] == '-') - { - if (json.Contains('.')) - { - double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result); - return result; - } - else - { - _ = int.TryParse(json, out int result); - return result; - } - } - - if (json == "true") - { - return true; - } - - if (json == "false") - { - return false; - } - - // handles json == "null" as well as invalid JSON - return null; - } - - private static Dictionary CreateMemberNameDictionary(T[] members) - where T : MemberInfo - { - Dictionary nameToMember = new(StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < members.Length; i++) - { - /*T member = members[i]; - if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true)) - continue; - - string name = member.Name; - if (member.IsDefined(typeof(DataMemberAttribute), true)) - { - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true); - if (!string.IsNullOrEmpty(dataMemberAttribute.Name)) - name = dataMemberAttribute.Name; - } - - nameToMember.Add(name, member);*/ - // The above code is not working with .Net framework 2.0, so we ignore these attributes for compatibility reasons: - nameToMember.Add(members[i].Name, members[i]); - } - - return nameToMember; - } - - private static object ParseObject(Type type, string json) - { - object instance = FormatterServices.GetUninitializedObject(type); - - // The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON - List elems = Split(json); - if (elems.Count % 2 != 0) - { - return instance; - } - - if (!fieldInfoCache.TryGetValue(type, out Dictionary nameToField)) - { - nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); - fieldInfoCache.Add(type, nameToField); - } - - if (!propertyInfoCache.TryGetValue(type, out Dictionary nameToProperty)) - { - nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); - propertyInfoCache.Add(type, nameToProperty); - } - - for (int i = 0; i < elems.Count; i += 2) - { - if (elems[i].Length <= 2) - { - continue; - } - - string key = elems[i][1..^1]; - string value = elems[i + 1]; - - if (nameToField.TryGetValue(key, out FieldInfo fieldInfo)) - { - fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value)); - } - else if (nameToProperty.TryGetValue(key, out PropertyInfo propertyInfo)) - { - propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null); - } - } - - return instance; - } - } +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.Helpers.Updater +{ + // The MIT License (MIT) + + // Copyright (c) 2018 Alex Parker + // Copyright (c) 2018-2023 Peter Kirmeier + + // Permission is hereby granted, free of charge, to any person obtaining a copy of + // this software and associated documentation files (the "Software"), to deal in + // the Software without restriction, including without limitation the rights to + // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + // the Software, and to permit persons to whom the Software is furnished to do so, + // subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + using System; + using System.Collections; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.Serialization; + using System.Text; + + // Really simple JSON parser in ~300 lines + // - Attempts to parse JSON files with minimal GC allocation + // - Nice and simple "[1,2,3]".FromJson>() API + // - Classes and structs can be parsed too! + // class Foo { public int Value; } + // "{\"Value\":10}".FromJson() + // - Can parse JSON without type information into Dictionary and List e.g. + // "[1,2,3]".FromJson().GetType() == typeof(List) + // "{\"Value\":10}".FromJson().GetType() == typeof(Dictionary) + // - No JIT Emit support to support AOT compilation on iOS + // - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead. + // - Only public fields and property setters on classes/structs will be written to + // + // Limitations: + // - No JIT Emit support to parse structures quickly + // - Limited to parsing <2GB JSON files (due to int.MaxValue) + // - Parsing of abstract classes or interfaces is NOT supported and will throw an exception. + public static class JSONParser + { + [ThreadStatic] + private static Stack>? splitArrayPool; + [ThreadStatic] + private static StringBuilder? stringBuilder; + [ThreadStatic] + private static Dictionary>? fieldInfoCache; + [ThreadStatic] + private static Dictionary>? propertyInfoCache; + + public static T? FromJson(this string json) + { + // Initialize, if needed, the ThreadStatic variables + propertyInfoCache ??= new Dictionary>(); + fieldInfoCache ??= new Dictionary>(); + stringBuilder ??= new StringBuilder(); + splitArrayPool ??= new Stack>(); + + // Remove all whitespace not within strings to make parsing simpler + stringBuilder.Length = 0; + for (int i = 0; i < json.Length; i++) + { + char c = json[i]; + if (c == '"') + { + i = AppendUntilStringEnd(true, i, json); + continue; + } + + if (char.IsWhiteSpace(c)) + { + continue; + } + + stringBuilder.Append(c); + } + + // Parse the thing! + return (T?)ParseValue(typeof(T), stringBuilder.ToString()); + } + + internal static object? ParseValue(Type type, string json) + { + // Initialize, if needed, the ThreadStatic variables + propertyInfoCache ??= new Dictionary>(); + fieldInfoCache ??= new Dictionary>(); + stringBuilder ??= new StringBuilder(); + splitArrayPool ??= new Stack>(); + + if (type == typeof(string)) + { + if (json.Length <= 2) + { + return string.Empty; + } + + StringBuilder parseStringBuilder = new(json.Length); + for (int i = 1; i < json.Length - 1; ++i) + { + if (json[i] == '\\' && i + 1 < json.Length - 1) + { + int j = "\"\\nrtbf/".IndexOf(json[i + 1]); + if (j >= 0) + { + parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]); + ++i; + continue; + } + + if (json[i + 1] == 'u' && i + 5 < json.Length - 1) + { + if (uint.TryParse(json.AsSpan(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out uint c)) + { + parseStringBuilder.Append((char)c); + i += 5; + continue; + } + } + } + + parseStringBuilder.Append(json[i]); + } + + return parseStringBuilder.ToString(); + } + + if (type.IsPrimitive) + { + var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture); + return result; + } + + if (type == typeof(decimal)) + { + decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out decimal result); + return result; + } + + if (json == "null") + { + return null; + } + + if (type.IsEnum) + { + if (json[0] == '"') + { + json = json[1..^1]; + } + + try + { + return Enum.Parse(type, json, false); + } + catch + { + return 0; + } + } + + if (type.IsArray) + { + Type arrayType = type.GetElementType() !; + if (json[0] != '[' || json[^1] != ']') + { + return null; + } + + List elems = Split(json); + Array newArray = Array.CreateInstance(arrayType, elems.Count); + for (int i = 0; i < elems.Count; i++) + { + newArray.SetValue(ParseValue(arrayType, elems[i]), i); + } + + splitArrayPool.Push(elems); + return newArray; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) + { + Type listType = type.GetGenericArguments()[0]; + if (json[0] != '[' || json[^1] != ']') + { + return null; + } + + List elems = Split(json); + var list = (IList?)type.GetConstructor(new Type[] { typeof(int) })?.Invoke(new object[] { elems.Count }); + if (list == null) + { + return null; + } + + for (int i = 0; i < elems.Count; i++) + { + list.Add(ParseValue(listType, elems[i])); + } + + splitArrayPool.Push(elems); + return list; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + Type keyType, valueType; + { + Type[] args = type.GetGenericArguments(); + keyType = args[0]; + valueType = args[1]; + } + + // Refuse to parse dictionary keys that aren't of type string + if (keyType != typeof(string)) + { + return null; + } + + // Must be a valid dictionary element + if (json[0] != '{' || json[^1] != '}') + { + return null; + } + + // The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON + List elems = Split(json); + if (elems.Count % 2 != 0) + { + return null; + } + + var dictionary = (IDictionary?)type.GetConstructor(new Type[] { typeof(int) })?.Invoke(new object[] { elems.Count / 2 }); + if (dictionary == null) + { + return null; + } + + for (int i = 0; i < elems.Count; i += 2) + { + if (elems[i].Length <= 2) + { + continue; + } + + string keyValue = elems[i][1..^1]; + object? val = ParseValue(valueType, elems[i + 1]); + dictionary.Add(keyValue, val); + } + + return dictionary; + } + + if (type == typeof(object)) + { + return ParseAnonymousValue(json); + } + + if (json[0] == '{' && json[^1] == '}') + { + return ParseObject(type, json); + } + + return null; + } + + private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json) + { + stringBuilder!.Append(json[startIdx]); + for (int i = startIdx + 1; i < json.Length; i++) + { + if (json[i] == '\\') + { + if (appendEscapeCharacter) + { + stringBuilder.Append(json[i]); + } + + stringBuilder.Append(json[i + 1]); + i++; // Skip next character as it is escaped + } + else if (json[i] == '"') + { + stringBuilder.Append(json[i]); + return i; + } + else + { + stringBuilder.Append(json[i]); + } + } + + return json.Length - 1; + } + + // Splits { :, : } and [ , ] into a list of strings + private static List Split(string json) + { + List splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List(); + splitArray.Clear(); + if (json.Length == 2) + { + return splitArray; + } + + int parseDepth = 0; + stringBuilder!.Length = 0; + for (int i = 1; i < json.Length - 1; i++) + { + switch (json[i]) + { + case '[': + case '{': + parseDepth++; + break; + case ']': + case '}': + parseDepth--; + break; + case '"': + i = AppendUntilStringEnd(true, i, json); + continue; + case ',': + case ':': + if (parseDepth == 0) + { + splitArray.Add(stringBuilder.ToString()); + stringBuilder.Length = 0; + continue; + } + + break; + } + + stringBuilder.Append(json[i]); + } + + splitArray.Add(stringBuilder.ToString()); + + return splitArray; + } + + private static object? ParseAnonymousValue(string json) + { + if (json.Length == 0) + { + return null; + } + + if (json[0] == '{' && json[^1] == '}') + { + List elems = Split(json); + if (elems.Count % 2 != 0) + { + return null; + } + + var dict = new Dictionary(elems.Count / 2); + for (int i = 0; i < elems.Count; i += 2) + { + dict.Add(elems[i][1..^1], ParseAnonymousValue(elems[i + 1])); + } + + return dict; + } + + if (json[0] == '[' && json[^1] == ']') + { + List items = Split(json); + var finalList = new List(items.Count); + for (int i = 0; i < items.Count; i++) + { + finalList.Add(ParseAnonymousValue(items[i])); + } + + return finalList; + } + + if (json[0] == '"' && json[^1] == '"') + { + return ParseValue(typeof(string), json); // fix https://github.com/zanders3/json/issues/29 + } + + if (char.IsDigit(json[0]) || json[0] == '-') + { + if (json.Contains('.')) + { + double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double result); + return result; + } + else + { + _ = int.TryParse(json, out int result); + return result; + } + } + + if (json == "true") + { + return true; + } + + if (json == "false") + { + return false; + } + + // handles json == "null" as well as invalid JSON + return null; + } + + private static Dictionary CreateMemberNameDictionary(T[] members) + where T : MemberInfo + { + Dictionary nameToMember = new(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < members.Length; i++) + { + T member = members[i]; + if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true)) + { + continue; + } + + string name = member.Name; + if (member.IsDefined(typeof(DataMemberAttribute), true)) + { + DataMemberAttribute? dataMemberAttribute = (DataMemberAttribute?)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true); + if (!string.IsNullOrEmpty(dataMemberAttribute?.Name)) + { + name = dataMemberAttribute.Name; + } + } + + nameToMember.Add(name, member); + } + + return nameToMember; + } + + private static object ParseObject(Type type, string json) + { + object instance = FormatterServices.GetUninitializedObject(type); + + // The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON + List elems = Split(json); + if (elems.Count % 2 != 0) + { + return instance; + } + + if (!fieldInfoCache!.TryGetValue(type, out Dictionary? nameToField)) + { + nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); + fieldInfoCache.Add(type, nameToField); + } + + if (!propertyInfoCache!.TryGetValue(type, out Dictionary? nameToProperty)) + { + nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); + propertyInfoCache.Add(type, nameToProperty); + } + + for (int i = 0; i < elems.Count; i += 2) + { + if (elems[i].Length <= 2) + { + continue; + } + + string key = elems[i][1..^1]; + string value = elems[i + 1]; + + if (nameToField.TryGetValue(key, out FieldInfo? fieldInfo)) + { + fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value)); + } + else if (nameToProperty.TryGetValue(key, out PropertyInfo? propertyInfo)) + { + propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null); + } + } + + return instance; + } + } } \ No newline at end of file