Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 024ceeb

Browse files
committed
Add initial support for CSV deserialization
1 parent 509d4a7 commit 024ceeb

File tree

7 files changed

+838
-189
lines changed

7 files changed

+838
-189
lines changed

src/ServiceStack.Text/CsvReader.cs

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using ServiceStack.Reflection;
5+
using ServiceStack.Text.Common;
6+
using ServiceStack.Text.Jsv;
7+
8+
namespace ServiceStack.Text
9+
{
10+
public class CsvReader
11+
{
12+
static readonly ITypeSerializer Serializer = JsvTypeSerializer.Instance;
13+
14+
public static List<string> ParseLines(string csv)
15+
{
16+
var rows = new List<string>();
17+
if (string.IsNullOrEmpty(csv))
18+
return rows;
19+
20+
var withinQuotes = false;
21+
var lastPos = 0;
22+
23+
var i = -1;
24+
var len = csv.Length;
25+
while (++i < len)
26+
{
27+
var c = csv[i];
28+
if (c == JsWriter.QuoteChar)
29+
{
30+
var isLiteralQuote = i + 1 < len && csv[i + 1] == JsWriter.QuoteChar;
31+
if (isLiteralQuote)
32+
{
33+
i++;
34+
continue;
35+
}
36+
37+
withinQuotes = !withinQuotes;
38+
}
39+
40+
if (withinQuotes)
41+
continue;
42+
43+
if (c == JsWriter.LineFeedChar)
44+
{
45+
var str = i > 0 && csv[i - 1] == JsWriter.ReturnChar
46+
? csv.Substring(lastPos, i - lastPos - 1)
47+
: csv.Substring(lastPos, i - lastPos);
48+
49+
if (str.Length > 0)
50+
rows.Add(str);
51+
lastPos = i + 1;
52+
}
53+
}
54+
55+
if (i > lastPos)
56+
{
57+
var str = csv.Substring(lastPos, i - lastPos);
58+
if (str.Length > 0)
59+
rows.Add(str);
60+
}
61+
62+
return rows;
63+
}
64+
65+
public static List<string> ParseFields(string line)
66+
{
67+
var to = new List<string>();
68+
if (string.IsNullOrEmpty(line))
69+
return to;
70+
71+
var i = -1;
72+
var len = line.Length;
73+
while (++i <= len)
74+
{
75+
var value = Serializer.EatValue(line, ref i);
76+
to.Add(value.FromCsvField());
77+
}
78+
79+
return to;
80+
}
81+
}
82+
83+
public class CsvReader<T>
84+
{
85+
public const char DelimiterChar = ',';
86+
87+
public static List<string> Headers { get; set; }
88+
89+
internal static List<Action<T, object>> PropertySetters;
90+
internal static Dictionary<string, Action<T, object>> PropertySettersMap;
91+
92+
internal static List<ParseStringDelegate> PropertyConverters;
93+
internal static Dictionary<string, ParseStringDelegate> PropertyConvertersMap;
94+
95+
private static readonly ParseStringDelegate OptimizedReader;
96+
97+
static CsvReader()
98+
{
99+
//if (typeof(T) == typeof(string))
100+
//{
101+
// OptimizedReader = ReadRow;
102+
// return;
103+
//}
104+
105+
Reset();
106+
}
107+
108+
internal static void Reset()
109+
{
110+
Headers = new List<string>();
111+
112+
PropertySetters = new List<Action<T, object>>();
113+
PropertySettersMap = new Dictionary<string, Action<T, object>>(PclExport.Instance.InvariantComparerIgnoreCase);
114+
115+
PropertyConverters = new List<ParseStringDelegate>();
116+
PropertyConvertersMap = new Dictionary<string, ParseStringDelegate>(PclExport.Instance.InvariantComparerIgnoreCase);
117+
118+
var isDataContract = typeof(T).IsDto();
119+
foreach (var propertyInfo in TypeConfig<T>.Properties)
120+
{
121+
if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod() == null) continue;
122+
if (!TypeSerializer.CanCreateFromString(propertyInfo.PropertyType)) continue;
123+
124+
var propertyName = propertyInfo.Name;
125+
var setter = propertyInfo.GetValueSetter<T>();
126+
PropertySetters.Add(setter);
127+
128+
var converter = JsvReader.GetParseFn(propertyInfo.PropertyType);
129+
PropertyConverters.Add(converter);
130+
131+
if (isDataContract)
132+
{
133+
var dcsDataMemberName = propertyInfo.GetDataMemberName();
134+
if (dcsDataMemberName != null)
135+
propertyName = dcsDataMemberName;
136+
}
137+
138+
Headers.Add(propertyName);
139+
PropertySettersMap[propertyName] = setter;
140+
PropertyConvertersMap[propertyName] = converter;
141+
}
142+
}
143+
144+
internal static void ConfigureCustomHeaders(Dictionary<string, string> customHeadersMap)
145+
{
146+
Reset();
147+
148+
for (var i = Headers.Count - 1; i >= 0; i--)
149+
{
150+
var oldHeader = Headers[i];
151+
string newHeaderValue;
152+
if (!customHeadersMap.TryGetValue(oldHeader, out newHeaderValue))
153+
{
154+
Headers.RemoveAt(i);
155+
PropertySetters.RemoveAt(i);
156+
}
157+
else
158+
{
159+
Headers[i] = newHeaderValue.EncodeJsv();
160+
}
161+
}
162+
}
163+
164+
private static List<T> GetSingleRow(IEnumerable<string> rows, Type recordType)
165+
{
166+
var row = new List<T>();
167+
foreach (var value in rows)
168+
{
169+
var to = recordType == typeof(string)
170+
? (T)(object)value
171+
: TypeSerializer.DeserializeFromString<T>(value);
172+
173+
row.Add(to);
174+
}
175+
return row;
176+
}
177+
178+
public static List<T> GetRows(IEnumerable<string> records)
179+
{
180+
var rows = new List<T>();
181+
182+
if (records == null) return rows;
183+
184+
if (typeof(T).IsValueType() || typeof(T) == typeof(string))
185+
{
186+
return GetSingleRow(records, typeof(T));
187+
}
188+
189+
foreach (var record in records)
190+
{
191+
var to = typeof(T).CreateInstance<T>();
192+
foreach (var propertySetter in PropertySetters)
193+
{
194+
propertySetter(to, record);
195+
}
196+
rows.Add(to);
197+
}
198+
199+
return rows;
200+
}
201+
202+
public static object ReadObject(string csv)
203+
{
204+
if (csv == null) return null; //AOT
205+
206+
return Read(CsvReader.ParseLines(csv));
207+
}
208+
209+
public static object ReadObjectRow(string csv)
210+
{
211+
if (csv == null) return null; //AOT
212+
213+
return ReadRow(csv);
214+
}
215+
216+
public static List<T> Read(List<string> rows)
217+
{
218+
var to = new List<T>();
219+
if (rows == null || rows.Count == 0) return to; //AOT
220+
221+
if (typeof(T).IsAssignableFromType(typeof(Dictionary<string, object>)))
222+
{
223+
return CsvDictionaryWriter.ReadObjectDictionary(rows)
224+
.ConvertAll(x => (T)x.FromObjectDictionary(typeof(T)));
225+
}
226+
227+
228+
if (OptimizedReader != null)
229+
{
230+
foreach (var row in rows)
231+
{
232+
to.Add((T)OptimizedReader(row));
233+
}
234+
return to;
235+
}
236+
237+
List<string> headers = null;
238+
if (!CsvConfig<T>.OmitHeaders || Headers.Count == 0)
239+
headers = CsvReader.ParseFields(rows[0]);
240+
241+
if (typeof(T).IsValueType() || typeof(T) == typeof(string))
242+
{
243+
return GetSingleRow(rows, typeof(T));
244+
}
245+
246+
for (var rowIndex = headers == null ? 0 : 1; rowIndex < rows.Count; rowIndex++)
247+
{
248+
var row = rows[rowIndex];
249+
var o = typeof(T).CreateInstance<T>();
250+
251+
var fields = CsvReader.ParseFields(row);
252+
for (int i = 0; i < fields.Count; i++)
253+
{
254+
var setter = i < PropertySetters.Count ? PropertySetters[i] : null;
255+
if (headers != null)
256+
PropertySettersMap.TryGetValue(headers[i], out setter);
257+
258+
if (setter == null)
259+
continue;
260+
261+
var converter = i < PropertyConverters.Count ? PropertyConverters[i] : null;
262+
if (headers != null)
263+
PropertyConvertersMap.TryGetValue(headers[i], out converter);
264+
265+
if (converter == null)
266+
continue;
267+
268+
var field = fields[i];
269+
var convertedValue = converter(field);
270+
setter(o, convertedValue);
271+
}
272+
273+
to.Add(o);
274+
}
275+
276+
return to;
277+
}
278+
279+
public static T ReadRow(string value)
280+
{
281+
if (value == null) return default(T); //AOT
282+
283+
return Read(CsvReader.ParseLines(value)).FirstOrDefault();
284+
}
285+
286+
}
287+
}

0 commit comments

Comments
 (0)