From a5c02db8c8f3620e5b74932e1c3e45469c54a0c4 Mon Sep 17 00:00:00 2001 From: piffett Date: Sun, 17 Jul 2022 18:54:11 +0900 Subject: [PATCH 1/2] Support for classes with constructors --- CsvUtil.cs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/CsvUtil.cs b/CsvUtil.cs index d94ab23..420ddc6 100644 --- a/CsvUtil.cs +++ b/CsvUtil.cs @@ -65,6 +65,41 @@ public static class CsvUtil { } return ret; } + + // class does not have a constructor with empty arguments + // like this + // class foo{ + // string a {get;} + // public foo(string a){ + // this.a = a; + // } + // } + public static List LoadObjectsWithConstructor(string filename, bool strict = true) { + using (var stream = File.OpenRead(filename)) { + using (var rdr = new StreamReader(stream)) { + return LoadObjectsWithConstructor(rdr, strict); + } + } + } + + public static List LoadObjectsWithConstructor(TextReader rdr, bool strict = true) { + var ret = new List(); + string header = rdr.ReadLine(); + var fieldDefs = ParseHeader(header); + FieldInfo[] fi = typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + PropertyInfo[] pi = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + string line; + while((line = rdr.ReadLine()) != null) + { + var args = new List(); + // box manually to avoid issues with structs + if (ParseLineToObject(line, fieldDefs, fi, pi, args, strict)) { + object obj = Activator.CreateInstance(typeof(T), BindingFlags.CreateInstance, null, args.ToArray(), null); + ret.Add((T)obj); + } + } + return ret; + } // Load a CSV file containing fields for a single object from a file // No header is required, but it can be present with '#' prefix @@ -228,8 +263,9 @@ private static Dictionary ParseHeader(string header) { } // Parse an object line based on the header, return true if any fields matched - private static bool ParseLineToObject(string line, Dictionary fieldDefs, FieldInfo[] fi, PropertyInfo[] pi, object destObject, bool strict) { - + private static bool ParseLineToObject(string line, Dictionary fieldDefs, FieldInfo[] fi, PropertyInfo[] pi, object destObject, bool strict) + { + string[] values = EnumerateCsvLine(line).ToArray(); bool setAny = false; foreach(string field in fieldDefs.Keys) { @@ -243,6 +279,23 @@ private static bool ParseLineToObject(string line, Dictionary field } return setAny; } + private static bool ParseLineToObject(string line, Dictionary fieldDefs, FieldInfo[] fi, PropertyInfo[] pi, List args, bool strict) + { + + string[] values = EnumerateCsvLine(line).ToArray(); + bool setAny = false; + foreach(string field in fieldDefs.Keys) { + int index = fieldDefs[field]; + if (index < values.Length) { + string val = values[index]; + setAny = SetField(field, val, fi, pi, args) || setAny; + } else if (strict) { + Debug.LogWarning(string.Format("CsvUtil: error parsing line '{0}': not enough fields", line)); + } + } + return setAny; + } + private static bool SetField(string fieldName, string val, FieldInfo[] fi, PropertyInfo[] pi, object destObject) { bool result = false; @@ -268,6 +321,31 @@ private static bool SetField(string fieldName, string val, FieldInfo[] fi, Prope } return result; } + + private static bool SetField(string fieldName, string val, FieldInfo[] fi, PropertyInfo[] pi, List args) { + bool result = false; + foreach (PropertyInfo p in pi) { + // Case insensitive comparison + if (string.Compare(fieldName, p.Name, true) == 0) { + // Might need to parse the string into the property type + object typedVal = p.PropertyType == typeof(string) ? val : ParseString(val, p.PropertyType); + args.Add(typedVal); + result = true; + break; + } + } + foreach(FieldInfo f in fi) { + // Case insensitive comparison + if (string.Compare(fieldName, f.Name, true) == 0) { + // Might need to parse the string into the field type + object typedVal = f.FieldType == typeof(string) ? val : ParseString(val, f.FieldType); + args.Add(typedVal); + result = true; + break; + } + } + return result; + } private static object ParseString(string strValue, Type t) { var cv = TypeDescriptor.GetConverter(t); From cbe7ad5aea52ad64580ab455a047e5a9c9b06fe7 Mon Sep 17 00:00:00 2001 From: piffett Date: Sun, 17 Jul 2022 19:04:06 +0900 Subject: [PATCH 2/2] add test for classes with constructor --- Editor/TestCsvUtil.cs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Editor/TestCsvUtil.cs b/Editor/TestCsvUtil.cs index b13da4a..aecc958 100644 --- a/Editor/TestCsvUtil.cs +++ b/Editor/TestCsvUtil.cs @@ -388,6 +388,34 @@ public void TestLoadStruct() { } + + public class TestAutoImplementedClass + { + public ulong Id { get; } + public string Name { get; } + + public TestAutoImplementedClass(ulong id, string name) + { + Id = id; + Name = name; + } + } + [Test] + public void TestLoadAutoImplementedPropertiesClass() { + // + var csvData = @"Id, Name +1,a +2,b"; + List objs; + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(csvData))) { + using (var sr = new StreamReader(ms)) { + objs = CsvUtil.LoadObjectsWithConstructor(sr); + } + } + Assert.AreEqual(1, objs[0].Id); + Assert.AreEqual("a",objs[0].Name); + } } -} \ No newline at end of file +} +