How to solve problem with CSV Mapping using CsvHelper - c#

I have a CSV File with multiple fields(check below for format)
ArticleNumber;Shop1;Shop2;Shop3;Shop4;Shop5;Shop6;Shop7
123455;50;51;52;53;54;55;56
In fields Shop1,Shop2....Shop7 I have product Prices. I receive file like this, so I need to find a cool way to solve my problem.
I want to read this CSV using CsvHelper library, but I don't know how to map fields. As a result I want something like this:
ArticleNumber
Shop
Price
123455
Shop1
50
123455
Shop2
51
123455
Shop3
52
123455
Shop4
53
123455
Shop5
54
123455
Shop6
55

I think this will get you the format you are looking for.
void Main()
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ";"
};
using (var reader = new StringReader("ArticleNumber;Shop1;Shop2;Shop3;Shop4;Shop5;Shop6;Shop7\n123455;50;51;52;53;54;55;56\n123456;60;61;62;63;64;65;66"))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ArticleMap>();
csv.Read();
csv.ReadHeader();
var shops = csv.HeaderRecord.Skip(1).ToArray();
var records = csv.GetRecords<Article>().ToList();
var results = records.SelectMany(r => r.Shop
.Select((s, i) => new ArticleShop
{
ArticleNumber = r.ArticleNumber,
Shop = shops[i],
Price = s
})
).ToList();
}
}
public class ArticleMap : ClassMap<Article>
{
public ArticleMap()
{
Map(x => x.ArticleNumber);
Map(x => x.Shop).Index(1);
}
}
public class Article
{
public int ArticleNumber { get; set; }
public List<double> Shop { get; set; }
}
public class ArticleShop
{
public int ArticleNumber { get; set; }
public string Shop { get; set; }
public double Price { get; set; }
}

Is the format of your CSV File set, or can it be changed?
If it can be changed you could change it to
ArticleNumber;Shop;Price
123455;Shop1;50
123455;Shop2;51
and so on
Edit: As I said in my comment you could also do it like this (this is Pseudo code only, I dont have c# open)
class PriceForArticle{
int articleNumber;
string shopName;
float price;
}
and then you would have this Methode to initialise them into a list of PriceForArticle
List<PriceForArticle> prices = new List<PriceForArticle>();
for(int j = 1; j < AllArticles.Length; j++){
for(int i = 1; i < AllArticles[j].Length; i++){
prices.Add(new PriceForArticle(AllArticles[j][0], AllArticles[0][i], AllArticles[j][i]));
}
}

Related

Order lines from a text file by number found in a certain spot of the line

I'm sorry that I interrupt you in this manner, I'm new to C# and I've been struggling with this problem for days... Maybe it will seem easy for you :)
I have this text file in this format
name|ID|domain|grade|verdict
Ryan|502322|Computers|9,33|Undefined
Marcel|302112|Automatics|6,22|Undefined
Alex|301234|Computers|5,66|Undefined
Leo|201122|Automatics|3,22|Undefined
How can I sort the text file using any methods (including LINQ) so that the list from the text file will be ordered by domain, and then descending by the grade column? Like this:
name|ID|domain|grade|verdict
Marcel|302112|Automatics|6,22|Undefined
Leo|201122|Automatics|3,22|Undefined
Ryan|502322|Computers|9,33|Undefined
Alex|301234|Computers|5,66|Undefined
To read the file, I'm using var Students = File.ReadAllLines(#"filepath");, I don't know if it's the smartest approach, and then I write using File.WriteAllLines
Thanks in advance! Sorry once again, I know it should be easy, but for me is really tuff :(
You can use some thing like this:
var students= File.ReadAllLines(#"filepath");
var headers = lines[0];
students = lines.Skip(1).ToArray();
var orders = lines.Select(x => x.Split('|'))
.Select(x => new { Domain = x[2], Grade = int.Parse(x[3].Replace(",", "")), All = x })
.OrderBy(x => x.Domain).ThenByDescending(x => x.Grade).Select(x => string.Join("|", x.All)).ToList();
orders.Insert(0, headers);
students=orders.ToArray();
try following code:
private void ReadFile()
{
char Delimiter = '|';
string[] Lines = File.ReadAllLines(#"E:\RaftehHa.txt", Encoding.Default);
List<string[]> FileRows = Lines.Select(line =>
line.Split(new[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries)).ToList();
DataTable dt = new DataTable();
dt.Columns.AddRange(FileRows[0].Select(col => new DataColumn() { ColumnName = col }).ToArray());
FileRows.RemoveAt(0);
FileRows.ForEach(row => dt.Rows.Add(row));
DataView dv = dt.DefaultView;
dv.Sort = " ID ASC ";
dt = dv.ToTable();
dataGridView1.DataSource = dt;
}
A bit the same as already mentioned above, but as you mention you are new to C#, I have tried to add a little bit of structure to the code, but leaving the completion to you.
public class Data
{
public Data(string inputLine)
{
var split = inputLine.Split('|');
Name = split[0];
Id = int.Parse(split[1]);
Domain = split[2];
Grade = double.Parse(split[3].Replace(",", "."));
Verdict = split[4];
}
public string Name { get; }
public int Id { get; }
public string Domain { get; }
public double Grade { get; }
public string Verdict { get; }
}
public class DataFile
{
public static IEnumerable Read(string fileName)
{
var input = File.ReadAllLines(fileName);
return input.Skip(1).Select(p => new Data(p)); // skip header
}
public static void Write(IEnumerable data)
{
// todo :)
}
}
void Main()
{
var input = DataFile.Read(#"C:\Temp\ExampleData.txt");
var result = input.OrderBy(p => p.Domain).ThenByDescending(p => p.Grade);
DataFile.Write(result);
}
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
enum DomainType {
Automatics, // 0
Computers // 1
}
class Data {
public int Id { get; set; }
public string Name { get; set; }
public string Verdict { get; set; }
public DomainType Domain { get; set; }
public Tuple<int, int> Grade { get; set; }
}
public static class Program {
static IEnumerable<Data> FileContent(string path) {
string line;
using (var reader = File.OpenText(path))
{
bool skipHeader = false;
while((line = reader.ReadLine()) != null)
{
if (!skipHeader) {
skipHeader = true;
continue;
}
var fields = line.Split('|');
string name = fields[0];
int id = int.Parse(fields[1]);
var domain = (DomainType)Enum.Parse(typeof(DomainType), fields[2]);
var grade = Tuple.Create(int.Parse(fields[3].Split(',')[0]),
int.Parse(fields[3].Split(',')[1]));
string verdict = fields[4];
var data = new Data() {
Name = name, Id = id, Domain = domain, Grade = grade, Verdict = verdict };
yield return data;
}
}
}
public static void Main() {
var result = FileContent("path_to_file").OrderBy(data => data.Domain);
foreach (var line in result) {
Console.WriteLine(line.Name);
}
}
}

CSV reading in C#, I just want to read some fields

I want to read a csv file that has 9 columns with headers and many data rows below, but I am just interested in three of the columns, and they are not contiguous. I have tried with this code but it doesn't work, it stops in the foreach loop with a runtime exception from CsvHelper "'Field with name 'Y' does not exist. You can ignore missing fields by setting MissingFieldFound to null.'".
The csv file is like this:
FrameNO , Ttotal, TNo, X, Y , Z , Speed , Intensity, ILog ;
1 , 9 , 1 , 0.08, 1.4 , 0 , 0 , 78 , 19 , ;;
1 , 9 , 2 ,0.1 , 1.56 , 0 , 0 , 228 , 28, ;;
using CsvHelper;
namespace RadarPrototipo.Clases
{
public class Foo
{
public int FrameNO { get; set; }
public double Y { get; set; }
public int Intensity { get; set; }
}
class CCalc
{
public double Calc(int f)
{
double d=1.5;
int inten=0;
using (var reader = new StreamReader("C:/Users/Usuario/Desktop/Uni/AlumnoInterno/grab.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.Delimiter = ",";
var records = csv.GetRecords<Foo>();
csv.Configuration.HeaderValidated = null;
foreach (var Foo in records)
{
if (Foo.FrameNO == f)
{
if (Foo.Y < 1.8 && Foo.Y > 1.5)
{
if (Foo.Intensity > inten)
{
inten = Foo.Intensity;
d = Foo.Y;
}
}
}
}
}
return d;
}
}
}
The function analyses the data on those three columns and selects the best answer according to the conditions, then returns the value Y which is a distance.
Any help is really thanked.
The following works for me.
public class Program
{
public static void Main(string[] args)
{
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
using (StreamReader reader = new StreamReader(stream))
using (CsvReader csv = new CsvReader(reader))
{
writer.WriteLine("FrameNO,Column2,Y,Column4,Column5,Column6,Intensity,Column8,Column9");
writer.WriteLine("1,two,1.123,four,five,six,10,eight,nine");
writer.WriteLine("2,two,2.345,four,five,six,20,eight,nine");
writer.Flush();
stream.Position = 0;
var records = csv.GetRecords<Foo>();
foreach (var Foo in records)
{
Console.WriteLine(Foo.FrameNO);
}
}
Console.ReadLine();
}
}
public class Foo
{
public int FrameNO { get; set; }
public double Y { get; set; }
public int Intensity { get; set; }
}
One thing you might try is setting your delimiter. Your cultural default might not be a comma. Also, if you have spaces between your data and the commas, you will need to set the TrimOptions.
using (var csv = new CsvReader(reader))
{
csv.Configuration.Delimiter = ",";
csv.Configuration.TrimOptions = TrimOptions.Trim;
var records = csv.GetRecords<Foo>();

How to Read This Text File and store in a list using C#

The Text File Data is Like Below:
S.No Name Description Quantity Rate Discount Amount
1 Apple Friut is 12 24.02 0 242
Good for
health
2 Orange Friut 5 12.22 3 128
3 Banana Friut 5 12.22 3 128
4 Grapes Friut 5 12.22 3 128
I want to add all the Rows& Columns in list but Description column have multiple Rows in single item. How can I Solve this. I add My Existing Code Here:
My Existing Code is as follows:
class Program
{
static void Main(string[] args)
{
var dd = File.ReadAllLines(
"C:\\Users\\Trainee\\Desktop\\Saravanan_Test\\27.8.2018\\Inputfile.txt")
.Skip(1)
.Where(s => s.Length > 1)
.Select(x => splits(x)).ToList();
foreach (var item in dd)
{
Console.WriteLine(item.id+"\t"
+ item.Name+"\t"
+ item.Description+"\t"
+ item.Quantity+"\t"
+ item.Rate+"\t"
+ item.Discount+"\t"
+ item.Amount);
}
Console.ReadKey();
}
private static Class1 splits(string x)
{
var columns = x.Split('\t').Where(c => c != "").ToList();
return new Class1
{
id = Convert.ToInt32(columns[0]),
Name = columns[1],
Description = columns[2],
Quantity = Convert.ToInt32(columns[3]),
Rate = Convert.ToDouble(columns[4]),
Discount = Convert.ToInt32(columns[5]),
Amount = int.Parse(columns[6])
};
}
}
class Class1
{
public int id { get; set; }
public string Name { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public double Rate { get; set; }
public int Discount { get; set; }
public int Amount { get; set; }
}
I want to store data into list like:
list.Add(new{ sno=1, Name="Apple",
Description="Friut is good for Health",
Quantity=12, Rate=24.02, Discount=0,
Amount=242 });
Thanks in Advance.
NOTE: This solution is based on the file shared in question. Data is separated by spaces and format is not advisable to use. Answering to help person with content format he has. Tested and working.
static void Main(string[] args)
{
List<Data> list = new List<Data>();
var dd = File.ReadAllLines(#"C:\Users\XXXX\Desktop\test.txt")
.Skip(1)
.Where(s => s.Length > 1).ToList();
foreach (var item in dd)
{
var columns = item.Split('\t').Where(c => c.Trim() != string.Empty).ToList();
if (columns != null && columns.Count > 0)
{
int id;
if (int.TryParse(columns[0], out id))
{
list.Add(new Data()
{
id = Convert.ToInt32(columns[0]),
Name = columns[1],
Description = columns[2],
Quantity = Convert.ToInt32(columns[3]),
Rate = Convert.ToDouble(columns[4]),
Discount = Convert.ToInt32(columns[5]),
Amount = int.Parse(columns[6])
});
}
else
{
list.Last().Description += columns[0];
}
}
}
Console.ReadLine();
}

Is there a way to deserialize CSV to object, mapping by varied column names?

I would like to deserialize CSVs to objects, mapping by varied column names, as illustrated by the following examples:
Input 1
Id;Name;Document
1;Matheus;555777
2;Clarice;567890
Input 2
"Id_Person";"First_Name";"Phone"
3;"John";"999-9999"
public class People
{
public int PersonId { get; set; }
public string Name { get; set; }
public string Doc { get; set; }
}
Note that the column names change by file, and a column can even be missing.
I would like to map "Id" and "Id_Person" to the PersonId property, and so on.
How to do it?
Actually found something that solved my problem: CsvHelper
Setting up:
public sealed class PessoaCSVMap : ClassMap<Pessoas>
{
public PessoaCSVMap()
{
Map(m => m.NomeCompleto).Name("Nome", "Name");
Map(m => m.Documento).Name("Documento", "Doc", "CPF");
Map(m => m.Email1).Name("Email", "Email1", "E-mail", "E-mail1");
Map(m => m.PessoaId).Ignore();
}
}
Using:
const string CSV = "Nome;Email;bleu\nMatheus;matheus.lacerda#email.com.br;blau\nClarice;null;null";
CsvReader csv = new CsvReader(new StringReader(CSV));
csv.Configuration.RegisterClassMap<PessoaCSVMap>();
csv.Configuration.Delimiter = ";";
csv.Configuration.HeaderValidated = null;
csv.Configuration.MissingFieldFound = null;
List<Pessoas> pessoas = csv.GetRecords<Pessoas>().ToList();
Assuming you already can read a specific CSV Line, this one manages to give a lot of flexibility with some reflection and generics . Not a works for all method, but it's easy to adapt it for different cases :
public IEnumerable<T> ProccessCSV<T>(StreamReader document, char divider)
where T : class, new()
{
string currentLine = reader.ReadLine();
string[] usedHeaders = currentLine.Split(divider);
while ((currentLine = reader.ReadLine()) != null)
{
var fields = currentLine.Split(divider);
var result = new T();
for (var i = 0; i < usedHeaders.Length; i++)
{
var value = fields[i];
var propInfo = typeof(T).GetProperties()[i];
result.GetType()
.GetProperty(propInfo.Name)
.SetValue(result, value);
}
yield return result;
}
}
Hope this works as a backbone to a possible solution.
For the first case, you'll need to use a separator ';' and a class like :
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Document { get; set; }
}
Based on : https://www.pluralsight.com/guides/microsoft-net/building-a-generic-csv-writer-reader-using-reflection

Issues with creating a JSON string

My brain is not working and I am trying to make something harder than I think it really should be and I need another set of eyes. I have the following in a text file
|TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37
|TS|170703/0505|170703/0905|40.22 -97.30 38.63 -97.30 38.19 -101.03 39.78 -101.03
what the above means...(|watchtype|watchstart|watchend| lat/long pairs)
The problem I'm having is that I need to take EACH ROW (could be 0 or could be 100+) and create a polygon on a map to mark the location of these storm watches. I currently have the following.
MODEL
public class WatchPolygons
{
public string WatchType { get; set; }
public string WatchStart { get; set; }
public string WatchEnd { get; set; }
public List<lat_longPairs> Lat_Long_Pairs {get; set;}
}
public class lat_longPairs
{
public decimal latitude { get; set; }
public decimal longitude { get; set; }
}
CONTROLLER
public JsonResult GetWatchPath()
{
var watchFilePaths = ConfigurationManager.AppSettings["watchFilePath"];
return Json(Directory.GetFiles(Server.MapPath(watchFilePaths), "current*.txt"), JsonRequestBehavior.AllowGet);
}
[HttpGet]
public ActionResult GetWatchData(string watchPath)
{
var stringData = new List<string>();
using (var reader = new StreamReader(watchPath))
{
while (!reader.EndOfStream)
{
var data = reader.ReadLine().Trim();
if (!string.IsNullOrEmpty(data))
stringData.Add(data);
}
}
return Json((from item in stringData
select item.Split(new char [] { '|' }, StringSplitOptions.RemoveEmptyEntries)
into rawData
select new WatchPolygons
{
WatchType = rawData[0],
WatchStart = rawData[1],
WatchEnd = rawData[2]
}).ToList(), JsonRequestBehavior.AllowGet);
}
I know I am missing the latlong pairs = rawData. I don't have it in the code because in the model it's a list and I can't easily convert the list to string the way I need it.
What am I missing? I believe I need to read over each line then read over each group to get the lat/long pairs. Just not sure.
You just need to parse rawData[3], containing the string with space separated lat/lon pairs. This is a naive implementation that will break when the input string does not contain pairs of numbers or when the current locale doesn't use a dot as a decimal separator:
private static List<lat_longPairs> ParseLatLon(string input)
{
var numbers = input.Split(new [] { " " }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => decimal.Parse(i))
.ToArray();
var latLonPairs = new List<lat_longPairs>();
for (int i = 0; i < numbers.Length; i += 2)
{
latLonPairs.Add(new lat_longPairs
{
latitude = numbers[i],
longitude = numbers[i + 1],
});
}
return latLonPairs;
}
Then call it from where you're projecting the polygons:
select new WatchPolygons
{
WatchType = rawData[0],
WatchStart = rawData[1],
WatchEnd = rawData[2],
Lat_Long_Pairs = ParseLatLon(rawData[3])
}
You may want to move the parsing code away from the controller as well, into its own class.
You can use GroupBy since two set make a coordinate.
var str = "|TS|170702/2300|170703/0503|42.80 -102.64 39.76 -102.64 39.44 -99.37 42.48 -99.37";
int itemsInGroup = 2;
var pairs = str.Split('|')[4].Split(' ').
// Give each set of coordinate a group number.
Select((n, i) => new { GroupNumber = i / itemsInGroup, Number = n }).
GroupBy(n => n.GroupNumber).
Select(g =>
{
var coordinate = g.Select(n => n.Number).ToList();
return new lat_longPairs
{
latitude = decimal.Parse(coordinate[0], NumberFormatInfo.InvariantInfo),
longitude = decimal.Parse(coordinate[1], NumberFormatInfo.InvariantInfo),
};
});

Categories