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

Commit b579bfc

Browse files
committed
Add support for serializing single Dictionary or KVP[] to CSV
1 parent 70f8e50 commit b579bfc

File tree

5 files changed

+132
-9
lines changed

5 files changed

+132
-9
lines changed

src/ServiceStack.Text/CsvSerializer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
@@ -256,6 +257,13 @@ private static WriteObjectDelegate GetWriteFn()
256257
bestCandidateEnumerableType = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>));
257258
if (bestCandidateEnumerableType != null)
258259
{
260+
var dictionarOrKvps = typeof(T).HasInterface(typeof(IEnumerable<KeyValuePair<string, object>>))
261+
|| typeof(T).HasInterface(typeof(IEnumerable<KeyValuePair<string, string>>));
262+
if (dictionarOrKvps)
263+
{
264+
return WriteSelf;
265+
}
266+
259267
var elementType = bestCandidateEnumerableType.GenericTypeArguments()[0];
260268
writeElementFn = CreateWriteFn(elementType);
261269

src/ServiceStack.Text/CsvWriter.cs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,36 @@ public static void WriteObjectRow(TextWriter writer, IEnumerable<object> row)
3232
writer.Write(CsvConfig.RowSeparatorString);
3333
}
3434

35+
public static void Write(TextWriter writer, IEnumerable<KeyValuePair<string, object>> records)
36+
{
37+
if (records == null) return; //AOT
38+
39+
var requireHeaders = !CsvConfig<IEnumerable<KeyValuePair<string, object>>>.OmitHeaders;
40+
if (requireHeaders)
41+
{
42+
var keys = records.Select(x => x.Key);
43+
WriteRow(writer, keys);
44+
}
45+
46+
var values = records.Select(x => x.Value);
47+
WriteObjectRow(writer, values);
48+
}
49+
50+
public static void Write(TextWriter writer, IEnumerable<KeyValuePair<string, string>> records)
51+
{
52+
if (records == null) return; //AOT
53+
54+
var requireHeaders = !CsvConfig<IEnumerable<KeyValuePair<string, string>>>.OmitHeaders;
55+
if (requireHeaders)
56+
{
57+
var keys = records.Select(x => x.Key);
58+
WriteRow(writer, keys);
59+
}
60+
61+
var values = records.Select(x => x.Value);
62+
WriteObjectRow(writer, values);
63+
}
64+
3565
public static void Write(TextWriter writer, IEnumerable<IDictionary<string, object>> records)
3666
{
3767
if (records == null) return; //AOT
@@ -230,12 +260,6 @@ public static void Write(TextWriter writer, IEnumerable<T> records)
230260
return;
231261
}
232262

233-
if (typeof(T) == typeof(Dictionary<string, object>) || typeof(T) == typeof(IDictionary<string, object>))
234-
{
235-
CsvDictionaryWriter.Write(writer, (IEnumerable<IDictionary<string, object>>)records);
236-
return;
237-
}
238-
239263
if (typeof(T).IsAssignableFromType(typeof(Dictionary<string, object>))) //also does `object`
240264
{
241265
var dynamicList = records.Select(x => x.ToObjectDictionary()).ToList();
@@ -292,7 +316,18 @@ public static void WriteRow(TextWriter writer, T row)
292316
{
293317
if (writer == null) return; //AOT
294318

295-
Write(writer, new[] { row });
319+
if (row is IEnumerable<KeyValuePair<string, object>> kvps)
320+
{
321+
CsvDictionaryWriter.Write(writer, kvps);
322+
}
323+
else if (row is IEnumerable<KeyValuePair<string, string>> kvpStrings)
324+
{
325+
CsvDictionaryWriter.Write(writer, kvpStrings);
326+
}
327+
else
328+
{
329+
Write(writer, new[] { row });
330+
}
296331
}
297332

298333
public static void WriteRow(TextWriter writer, IEnumerable<string> row)

src/ServiceStack.Text/StringExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,7 @@ public static int CountOccurrencesOf(this string text, char needle)
12071207

12081208
public static string NormalizeNewLines(this string text)
12091209
{
1210-
return text?.Replace("\r\n", "\n");
1210+
return text?.Replace("\r\n", "\n").Trim();
12111211
}
12121212

12131213
#if !LITE

tests/ServiceStack.Text.Tests/CsvSerializerTests.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,86 @@ public void Can_Serialize_using_custom_CSV_ItemString()
275275
CsvConfig.Reset();
276276
}
277277

278+
[Test]
279+
public void Can_serialize_ObjectDictionary_list()
280+
{
281+
var rows = new List<Dictionary<string, object>>
282+
{
283+
new Dictionary<string, object>
284+
{
285+
{ "Id", 1 },
286+
{ "CustomerId", "ALFKI" },
287+
},
288+
new Dictionary<string, object>
289+
{
290+
{ "Id", 2 },
291+
{ "CustomerId", "ANATR" },
292+
},
293+
};
294+
295+
Assert.That(rows.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI\n2,ANATR").Or.EqualTo("CustomerId,Id\nALFKI,1\nANATR,2"));
296+
}
297+
298+
[Test]
299+
public void Can_serialize_StringDictionary_list()
300+
{
301+
var rows = new List<Dictionary<string, string>>
302+
{
303+
new Dictionary<string, string>
304+
{
305+
{ "Id", "1" },
306+
{ "CustomerId", "ALFKI" },
307+
},
308+
new Dictionary<string, string>
309+
{
310+
{ "Id", "2" },
311+
{ "CustomerId", "ANATR" },
312+
},
313+
};
314+
315+
Assert.That(rows.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI\n2,ANATR").Or.EqualTo("CustomerId,Id\nALFKI,1\nANATR,2"));
316+
}
317+
318+
[Test]
319+
public void Can_serialize_single_ObjectDictionary_or_ObjectKvps()
320+
{
321+
var row = new Dictionary<string, object>
322+
{
323+
{ "Id", 1 },
324+
{ "CustomerId", "ALFKI" },
325+
};
326+
327+
Assert.That(row.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1"));
328+
329+
var kvps = new[]
330+
{
331+
new KeyValuePair<string, object>("Id", 1),
332+
new KeyValuePair<string, object>("CustomerId", "ALFKI"),
333+
};
334+
335+
Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1"));
336+
}
337+
338+
[Test]
339+
public void Can_serialize_single_StringDictionary_or_StringKvps()
340+
{
341+
var row = new Dictionary<string, string>
342+
{
343+
{ "Id", "1" },
344+
{ "CustomerId", "ALFKI" },
345+
};
346+
347+
Assert.That(row.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1"));
348+
349+
var kvps = new[]
350+
{
351+
new KeyValuePair<string, string>("Id", "1"),
352+
new KeyValuePair<string, string>("CustomerId", "ALFKI"),
353+
};
354+
355+
Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1"));
356+
}
357+
278358
[Test]
279359
public void serialize_Category()
280360
{

tests/ServiceStack.Text.Tests/DynamicJsonTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void Can_Serialize_dynamic_collection()
1515
Assert.That(json, Is.EqualTo("[{\"Name\":\"Foo\"},{\"Name\":\"Bar\"}]"));
1616

1717
string csv = CsvSerializer.SerializeToString(rows);
18-
Assert.That(csv.NormalizeNewLines(), Is.EqualTo("Name\nFoo\nBar\n"));
18+
Assert.That(csv.NormalizeNewLines(), Is.EqualTo("Name\nFoo\nBar"));
1919
}
2020

2121
[Test]

0 commit comments

Comments
 (0)