Deserialize CSV with CustomHeaders using ServiceStack.Text: not working? - c#

consider following class:
class Foo {
public string bar { get; set; }
public string rab { get; set; }
public override string ToString()
{
return string.Format("[Foo bar={0}, rab={1}]", bar, rab);
}
}
And following code:
var csv1 =
"bar,rab" + Environment.NewLine +
"a,b" + Environment.NewLine +
"c,d" + Environment.NewLine +
"e,f" + Environment.NewLine;
var csv2 =
"xbar,xrab" + Environment.NewLine +
"a,b" + Environment.NewLine +
"c,d" + Environment.NewLine +
"e,f" + Environment.NewLine;
Console.WriteLine(CsvSerializer.DeserializeFromString<List<Foo>>(csv1).First());
CsvConfig<TMDBEntity>.CustomHeadersMap = new Dictionary<string, string> {
{"bar", "xbar"},
{"rab", "xrab"}
};
Console.WriteLine(CsvSerializer.DeserializeFromString<List<Foo>>(csv2).First());
It is printing:
[Foo bar=a, rab=b]
[Foo bar=, rab=]
while I would expect it to print
[Foo bar=a, rab=b]
[Foo bar=a, rab=b]
Why is it not picking up the custom headers using xbar and xrab?
ServiceStack.Text version is 4.5.14.0

This issue should be resolved from this commit which is available from ServiceStack v5.0.0 that's now available on MyGet.
Note you need to configure CsvConfig<Foo> in order to change how Foo is deserialized, e.g:
CsvConfig<Foo>.CustomHeadersMap = new Dictionary<string, string> {
{"bar", "xbar"},
{"rab", "xrab"}
};

Related

Assign List value to Json list class

I have a List class List<Data> dataValue = new List<Data> where Data contains List of destination's(multiple) and List of source's(multiple) details. I want to loop through and assign each of the destination and source to the List below. I am finally converting all the data to a JSON file.
foreach (var data in dataValue)
{
var value = new RuleJsonclassTemplate
{
type = data.type,
mapping = new List<Mapping>() { new Mapping() { value = data.destination, key = data.source } },
description = data.description,
title = data.title
}
}
string JSONresult = JsonConvert.SerializeObject(value);
string path = outputdir + Outputfilename;
using (var writer = new StreamWriter(path, true))
{
writer.WriteLine(JSONresult.ToString());
writer.Close();
}
class Mapping
{
public string destination { get; set; }
public string source { get; set; }
}
The JSON output should look like below,
{
"type": "Type1",
"mapping": [
{
"value": "destination1",
"key": "source1"
},
{
"value": "destination2",
"key": "source1"
},
{
"value": "destination3",
"key": "source3"
}
],
"description": "Test description",
"title": "Test title"
}
Can you please suggest on how can I achieve this? For reference my sample code at https://dotnetfiddle.net/W49buW
It would be :
public string ConvertToJSON(List<string>SourceStr,List<string>DestinationStr)
{
string json = "{" + Environment.NewLine + "mapping : [" + Environment.NewLine;
for (int i = 0; i < SourceStr.Count; i++)
{
json = json + "{" + Environment.NewLine + "value : " + SourceStr[i].ToString() + ","+ Environment.NewLine
+ "key : " + DestinationStr[i].ToString() + Environment.NewLine + "}," + Environment.NewLine;
}
json = json.Substring(0, json.LastIndexOf(','));
json = json + "]" + Environment.NewLine + "}";
return json;
}
I was able to fix the problem by adding another method which add's the data 1 by 1 to the list. Updated my sample code at https://dotnetfiddle.net/W49buW. Hope this helps for folks looking for the solution.

How do I fill a text box using a public string?

I have this frustrating problem that I can't seem to solve.
I try to fill a TextBox with text from a public static string.
But when I run the program it just shows a blank text box with nothing in it.
I don't have any errors so it's hard for me to understand what I'm doing wrong.
Here is the code I have:
public ShowMp3()
{
InitializeComponent();
OverzichttxtBox.Text = OverzichtMP3();
}
public static String OverzichtMP3()
{
String overzicht = "";
foreach (Mp3Player player in Mp3.GetPlayers())
overzicht = overzicht + "ID: " + Convert.ToString(player.id) + "\r\n" +
"Merk: " + player.make + "\r\n" + "Model: " + player.model +
"\r\n" + "MB-size: " + player.mBSize + "\r\n" + "Prijs: " +
player.price + "\r\n" + "\r\n";
return overzicht;
}
And Mp3.GetPlayers() is this:
private static ArrayList players = new ArrayList();
public static void Initialize()
{
Mp3Player player1 = new Mp3Player(1, "GET Technologies .inc", "HF 410", 4096, 129.95M, 500);
Mp3Player player2 = new Mp3Player(2, "Far & Loud", "XM 600", 8192, 224.95M, 500);
Mp3Player player3 = new Mp3Player(3, "Innotivative ", "Z3", 512, 79.95M, 500);
Mp3Player player4 = new Mp3Player(4, "Resistance S.A.", "3001", 4096, 124.95M, 500);
Mp3Player player5 = new Mp3Player(5, "CBA", "NXT Volume", 2048, 159.05M, 500);
players.Add(player1);
players.Add(player2);
players.Add(player3);
players.Add(player4);
players.Add(player5);
}
public static ArrayList GetPlayers()
{
return players;
}
I suspect the problem is you're never calling Mp3.Initialize(). You could add this to a static constructor in the Mp3 class:
private static List<Mp3Player> players = new List<Mp3Player>();
static Mp3()
{
Initialize();
}
// This can be private now...
private static void Initialize()
{
....
Note that you may want to change the ArrayList to a List<Mp3Player>, as well.
Also, as an aside, you should give StringBuilder and String.Format a try. Instead of this...
overzicht = overzicht + "ID: " + Convert.ToString(player.id) + "\r\n" +
"Merk: " + player.make + "\r\n" + "Model: " + player.model +
"\r\n" + "MB-size: " + player.mBSize + "\r\n" + "Prijs: " +
player.price + "\r\n" + "\r\n";
You can do:
StringBuilder overzicht = new StringBuilder();
overzicht.AppendLine(String.Format("ID: {0}", player.id));
overzicht.AppendLine(String.Format("Merk: {0}", player.make));
overzicht.AppendLine(String.Format("Model: {0}", player.model));
overzicht.AppendLine(String.Format("MB-size: {0}", player.mBSize));
overzicht.AppendLine(String.Format("Prijs: {0}", player.price));
return overzicht.ToString();
Much easier to read ;)
My String.Format isn't that compelling here... but if this was all on one line, it'd be much more useful:
return String.Format("ID:{0}, Merk:{1}, Model:{2}, MB-size:{3}, Prijs:{4}",
player.id, player.make, player.mode,
player.mBSize, player.price);

How do you display items from a list in a message box?

I am working on a project that needs to display a list of people that have above average income. The source data is a List<IncomeData> (id is the unique id for the person):
public struct IncomeData
{
public string id;
public double household;
public income;
}
public double belowAverage = 0, total, belowAveragePercent;
IncomeData surveyStruct;
List<IncomeData> surveyList = new List<IncomeData>();
List<string> aboveAverage = new List<string>();
Here is how I determine if a person has above average income. If a person has above average income, I add the id and income from the temporary instance of the surveyStruct to the above average list of string values:
//Determine poverty.
if (surveyStruct.income - 3480 * surveyStruct.household <= 6730)
{
belowAverage += 1;
}
else if (surveyStruct.income - 3480 * surveyStruct.household >= 6730)
{
aboveAverage.Add(surveyStruct.id);
aboveAverage.Add(surveyStruct.income.ToString());
}
And here is the code that displays the desired information in a message box. (The aboveAverage list is added in here, too.)
private void reportsToolStripMenuItem_Click(object sender, EventArgs e)
{
//Display reports 1, 2, and 3.
MessageBox.Show("Your Entry:\nID Code: " + surveyStruct.id +
"\nHousehold: " + surveyStruct.household.ToString() +
" people\nIncome: " + surveyStruct.income.ToString("C") +
"\n\nPeople Above Average:\n" + aboveAverage +
"\n\nAnd " + belowAveragePercent + "% of people are below average.");
}
Now, here's the problem: Instead of a seeing a list of values in the message box, I am seeing System.Collections.Generic.List`1[System.String] where the IDs and incomes of the above average people should be. Can somebody please tell me what I am doing wrong and how I can display a list values in a message box?
At the end of your question you ask: How I can display a List<IncomeData> in a message box?
So, the core of your question is converting your list of values to a string so that you can pass that string as an argument to MessageBox.Show().
The LINQ Extension Method Enumerable.Aggregate() offers an ideal solution for this problem. Say your List<IncomeData> looks something like this (I've omitted the household field for brevity):
var incomes = new List<IncomeData>() {
new IncomeData("abc0123", 15500),
new IncomeData("def4567", 12300),
new IncomeData("ghi8901", 17100)
};
The following LINQ query will convert that List<IncomeData> into a string:
string message = incomes.
Select(inc => inc.ToString()).
Aggregate((buffer, next) => buffer + "\n" + next.ToString());
To eliminate the need to call Select(), you can instead use the two-argument version of Enumerable.Aggregate(). This approach also allows you to specify a heading as the seed value for your accumulator:
string message2 = incomes.
Aggregate(
"Income data per person:",
(buffer, next) => buffer + "\n" + next.ToString());
That is equivalent to the following where the argument types have been made explicit:
string message = incomes.
Aggregate<IncomeData, string>(
"Income data per person:",
(string buffer, IncomeData next) => buffer + "\n" + next.ToString());
See the following (and online demo) for a complete working example preceded by its expected output.
Expected Output
Income data per person:
Id: abc0123, Income:15500
Id: def4567, Income:12300
Id: ghi8901, Income:17100
Demonstration Program
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqAggregateDemo
{
public class Program
{
public static void Main(string[] args)
{
var incomes = new List<IncomeData>() {
new IncomeData("abc0123", 15500),
new IncomeData("def4567", 12300),
new IncomeData("ghi8901", 17100)
};
string message = incomes.
Select(inc => inc.ToString()).
Aggregate((buffer, next) => buffer + "\n" + next.ToString());
Console.WriteLine("Income data per person:\n" + message);
}
public struct IncomeData
{
public readonly string Id;
public readonly int Income;
public IncomeData(string id, int income)
{
this.Id = id;
this.Income = income;
}
public override string ToString()
{
return String.Format(
"Id: {0}, Income:{1}",
this.Id,
this.Income);
}
}
}
}
First, make aboveAverage a List<IncomeData>, and add the IncomeDatas that match into that list.
Then, you need to define a ToString for your custom struct, something like this:
public override void string ToString()
{
return string.Format("The id is {0}, the household is {1} and the income is {2}.", id, household, income);
}
Then, in your MessageBox.Show call, you need to replace aboveAverage with
aboveAverage.Aggregate((a,b) => a.ToString() + Enviroment.NewLine + b.ToString())
Should make it show properly.
Sorry about the formatting,I'm on mobile.
StringBuilder is one choice:
StringBuilder aboveAverage = new StringBuilder();
//Determine poverty.
if (surveyStruct.income - 3480 * surveyStruct.household <= 6730)
{
belowAverage += 1;
}
else if (surveyStruct.income - 3480 * surveyStruct.household >= 6730)
{
aboveAverage.Append(string.Format("id: %s, income: %s\n",
surveyStruct.id, surveyStruct.income.ToString());
}
And you will need a ToString() for the string builder, like this:
MessageBox.Show("Your Entry:\nID Code: " + surveyStruct.id + "\nHousehold: " + surveyStruct.household.ToString() + " people\nIncome: " + surveyStruct.income.ToString("C") + "\n\nPeople Above Average:\n" + aboveAverage.ToString() + "\n\nAnd " + belowAveragePercent + "% of people are below average.");
You could do it with join if you leave aboveAverage as a list, like this:
string.Join(aboveAverage,Environment.NewLine);
In your current code -- but that would not look so nice.
You could also do it with Linq, you want to see that?
Ok, here is a sexy one line version: (all questions should have a one line linq answer):
(the using and indent don't count, they are just there to make the code more readable!)
using NL = Environment.NewLine;
    
string indent = "    ";
MessageBox.Show(
"Your Entry:" + NL +
"ID Code: " + surveyStruct.id + NL +
"Household: " + surveyStruct.household.ToString() + " people" + NL +
"Income: " + surveyStruct.income.ToString("C") + NL + NL +
"People Above Average:" + NL +
indent + string.Join(NL+indent,
surveyList.Where(s => (s.income - 3480) * s.household >= 6730)
.Select(s => "ID: "+s.id+" $"+s.income.ToString).ToArray()) + NL +
"And " + (surveyList.Where(s => ((s.income - 3480) * s.household) <= 6730).Count() / surveyList.Count()) * 100 + "% of people are below average.");

How to loop through properties of objects in c#?

I have a method which gets the values of the properties of an object and appends some commas to it. I want to make this generinc so i can use it with other objects.
foreach (var row in rows.ToList())
{
sbResult.Append(
delimiter + row.MediaName + delimiter + separator +
delimiter + row.CountryName + delimiter + separator +
delimiter + row.ItemOverRideDate + delimiter + separator +
delimiter + row.Rating + delimiter + separator +
delimiter + row.BatchNo + delimiter + separator +
delimiter + row.NoInBatch + delimiter + separator +
delimiter + row.BatchDate + delimiter + separator +
delimiter + row.DataType + delimiter + separator +
delimiter + row.ByLine + delimiter + separator +
delimiter + row.IssueNo + delimiter + separator +
delimiter + row.Issue + delimiter + separator +
delimiter + row.MessageNo + delimiter + separator +
delimiter + row.Message + delimiter + separator +
delimiter + row.SourceName + delimiter + separator +
delimiter + row.SourceType + delimiter + separator);
//end of each row
sbResult.AppendLine();
}
I have tried using var rowData = row.GetType().GetProperties(); but it only returns the property itself and I dont know how to get the value of the property.
Since Type.GetProperties returns a collection of PropertyInfo, you follow that up by calling PropertyInfo.GetValue. Here's how you can do that (and all the rest together) with LINQ:
var line = string.Join(
row.GetType().GetProperties()
.Select(pi => pi.GetValue(row))
.Select(v => delimiter + v.ToString() + delimiter),
separator);
However, you might want to reconsider your approach. This code will break if GetProperties fetches static properties or indexers along with "normal" properties; it also requires that the code be run with full trust (otherwise no reflection is possible). And finally, it's going to be slow because a) reflection is inherently slow and b) it will keep reflecting on the same things over and over again without caching any of the information it has already discovered.
In addition to the above potential problems, if there is even a remote chance that you will later want to filter what gets printed out it is probably better to encapsulate this logic inside a (virtual?) method on row and just do something like
sbResult.AppendLine(row.SerializeAsLine());
You can use something like this to iterate over all the properties of a particular type:
public static IEnumerable<KeyValuePair<string, T>> PropertiesOfType<T>(object obj)
{
return from p in obj.GetType().GetProperties()
where p.PropertyType == typeof(T)
select new KeyValuePair<string, T>(p.Name, (T)p.GetValue(obj));
}
Then you could specify the type as string for all your string properties.
Compilable sample:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
private static void Main()
{
var test = new Test
{
Str1 = "S1",
Str2 = "S2",
Str3 = "S3",
Str4 = "S4"
};
foreach (var property in PropertiesOfType<string>(test))
{
Console.WriteLine(property.Key + ": " + property.Value);
}
}
public static IEnumerable<KeyValuePair<string, T>> PropertiesOfType<T>(object obj)
{
return from p in obj.GetType().GetProperties()
where p.PropertyType == typeof(T)
select new KeyValuePair<string, T>(p.Name, (T)p.GetValue(obj));
}
}
public class Test
{
public string Str1 { get; set; }
public string Str2 { get; set; }
public string Str3 { get; set; }
public string Str4 { get; set; }
}
}
Here it is.
List<PropertyInfo> _propInfo = _row.GetType().GetProperties();
foreach (var item in _propInfo)
{
object _value = item.GetValue(_row, null);
if (_value != null)
{
// Save the Value
}
}
GetProperties returns an array of PropertyInfo, so use the GetValue method and use your object as it's input to get the value for each property. Here is the code:
public class MyClass
{
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
Then
MyClass myObj = new MyClass() { MyProperty1 = "first", MyProperty2 = "second", MyProperty3 = "third" };
List<string> array = new List<string>();
foreach (var item in typeof(MyClass).GetProperties())
{
array.Add(item.GetValue(myObj, null).ToString());
}
var result = string.Join(",", array); //use your own delimiter
var values = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(z => string.Format("{0}: {1}\n", z.Name, z.GetValue(instance, null)));
string res = string.Concat(values);
Where instance is the instance of your object. You might want to avoid LINQ and use a loop if StringBuilder is required (depending on the number of properties).

Delimiter Replacement Approach

I’d like to create a method to replace delimiters for the intended target use (html email, log, database). The delimiters are constant so I’d like to be able to reference a object that maps recognizable names to string values (semicolon = “;”, htmlLineBreak = “<br/>”, etc.). Is there a better means to do this than this below?
public static class Utilities
{
public string ReplaceDelimiter(string content
, Delimiter currentDelimiter, Delimiter outputDelimiter)
{
return content.Replace(currentDelimiter.ToString()
, outputDelimiter.ToString());
}
}
public class Delimiter
{
public const string comma = ",";
public const string semicolon = ";";
public const string colon = ":";
public const string lineBreak = "\r\n";
public const string htmlLineBreak = "<br/>";
}
Edited following comments:
A use case would be when I want to log an error to different targets and send the same contents (formatted differently) in an email. The log may go to a database column (want key/value with semicolon delimiter) or log file (want delimiter to be line breaks). The email would be HTML so want the delimiter to be replaced with <br/>.
Below would be an excerpt from a logging method that has a few parameters including the actual Exception:
StringBuilder delimitedMessage = new StringBuilder();
delimitedMessage.Append("Date=" + DateTime.Now.ToShortDateString() + ";");
delimitedMessage.Append("Time=" + DateTime.Now.ToLongTimeString() + ";");
delimitedMessage.Append("Source=" + objException.Source.ToString().Trim() + ";");
delimitedMessage.Append("Method=" + objException.TargetSite.Name.ToString() + ";");
delimitedMessage.Append("Erring Method=" + methodName + ";");
delimitedMessage.Append("Computer=" + System.Environment.MachineName.ToString() + ";");
delimitedMessage.Append("Log Message=" + logMessage + ";");
delimitedMessage.Append("Exception Error=" + objException.Message.ToString().Trim() + ";");
delimitedMessage.Append("Severity=" + severity.ToString() + ";");
delimitedMessage.Append("Stack Trace=" + objException.StackTrace.ToString().Trim() + ";");
contentToLog = delimitedMessage.ToString();
WriteToDb(contentToLog);
WriteToLog(Utilities.ReplaceDelimiter(contentToLog, Delimiter.semicolon, Delimiter.lineBreak));
SendEmail(Utilities.ReplaceDelimiter(contentToLog, Delimiter.semicolon, Delimiter.htmlLineBreak));
Code
public class Delimiter {
public static readonly Delimiter
HtmlLineBreak=new Delimiter {
Value="<br/>"
},
LineBreak=new Delimiter {
Value="\r\n"
},
Semicolon=new Delimiter {
Value=";"
},
Colon=new Delimiter {
Value=":"
},
Comma=new Delimiter {
Value=","
};
public override String ToString() {
return Value;
}
public String Value {
get;
set;
}
}
Test
var t=Utilities.ReplaceDelimiter("123\r\n", Delimiter.LineBreak, Delimiter.HtmlLineBreak);
Debug.Print("{0}", t);
Output
123<br/>

Categories

Resources