While setting up applications to communicate between various different trade partners and our WMS software I often have to parse very similar information from various different file formats. In this specific case i have to retrieve the tracking data from different files in csv, xml or json format. I've set up one template class for each file format to map to the respective fields. Each template derives from TrackingInformationTemplate
class. Now I have come up with this code to select the specific template i need for the parsing.
using CommandLine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace RBHelper
{
[Verb("versanddatenimport")]
class TrackinDataImportCommand : ICommand
{
[Option('i',"input", Required = true)]
public string FileName { get; set; }
[Option('v',"vorlage")]
public string Template{ get; set; }
private Dictionary<DateiEndung, Func<List<TrackingInformations>>> _convertOperations;
public TrackinDataImportCommand()
{
_convertOperations = new Dictionary<FileExtension, Func<List<TrackingInformations>>>()
{
{FileExtension.csv, ()=> FromCsv()},
{FileExtension.json, ()=> FromJson()},
{FileExtension.xml, ()=> FromXml()}
};
}
public void Execute()
{
// load TrackinData
List<TrakingInformations> vis = ParseVis();
// TODO: Working with loaded data
}
private List<TrackingInformations> ParseVis()
{
FileExtension ext = (FileExtension)Enum.Parse(typeof(FileExtension), Path.GetExtension(FileName).Substring(1));
if(!_convertOperations.Keys.Contains(ext))
throw new NotSupportedException($"{ext} not supported.");
return _convertOperations[ext]();
}
private List<TrackingInformations> FromCsv()
{
CsvtrackingInformationTemplate template= CsvTrackingInformationTemplate.Default;
if (!string.IsNullOrEmpty(Template)) template= TrackingInformationTemplate.Load<CsvTrackingInformationTemplate>(Template);
return template.DeserializeFile(FileName);
}
private List<TrackingInformations> FromJson()
{
JsonTrackingInformationTemplate template = JsonTrackingInformationTemplate.Default;
if (!string.IsNullOrEmpty(Template)) template = TrackingInformationTemplate.Load<JsonTrackingInformatioTemplate>(Template);
return template.DeserializeFile(FileName);
}
private List<TrackingInformations> FromXml()
{
XmlTrackingInformationTemplate template = XmlTrackingInformationTemplate.Default;
if (!string.IsNullOrEmpty(Temlate)) template = TrackingInformationTemplate.Load<XmlTrackingInformationTemplate>(Template);
return template.DeserializeFile(FileName);
}
}
}
Here is the Enum I use for file extension selection:
[JsonConverter(typeof(StringEnumConverter))]
public enum FileExtension
{
csv,
xml,
json
}
I am considering extracting the code related to the parsing into its own class. I just don't know how I would refactor the selection of the templates (FromCsv, FromXml, FromJson). these methods are near identical and i am certain that they can be improved.
Extra: I feel this might be outside of the scope of my question. For completion sake I'll show you the VersandInformationenVorlage
cass and one of the dreived classes to demonstrate how those work:
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
namespace RBHelper
{
abstract class TrackingInformationTemplate
{
public FileExtension FileExtension { get; private set; }
public int kUser;
public string CustomInfo;
public string CustomRoute;
public TrackingInformationTemplate(FileExtension FileExtension) => FileExtension = FileExtension;
public static T Load<T>(string fileName) where T : TrackingInformationTemplate=> JsonConvert.DeserializeObject<T>(File.ReadAllText(fileName));
public void Save(string fileName) => File.WriteAllText(fileName, JsonConvert.SerializeObject(this, Formatting.Indented));
public abstract List<TrackingInformations> DeserializeFile(string fileName);
protected List<TrackingInformations> Deserialize(List<TrackingInformations> input)
{
List<TrackingInformations> output = new List<TrackingInformations>();
foreach (TrackingInformations v in input)
if (!output.Contains(v)) output.Add(v);
return output;
}
protected void Serialize(string serialized, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(serialized);
}
}
}
}
And here the JsonTrackingInformationTemplate
class
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace RBHelper
{
class JsonTrackingInformationTemplate: TrackingInformationTemplate
{
public JsonTrackingInformationTemplate() : base( DateiEndung.json) { }
public string RootNode;
public string TrackingDate;
public string Id;
public string Info;
public string TrackingId;
public string Route;
public static JsonTrackingInformationTemplate Default => new JsonTrackingInformationTemplate()
{
kUser = 1,
RootNode = "[*]",
TrackingDate = "Versanddatum",
Id = "Id",
Info = "Versandinfo",
TrackingId = "TrackingId",
Route = "Route"
};
public override List<TrackingInformations> DeserializeFile(string fileName)
{
JToken doc = JToken.Parse(File.ReadAllText(fileName));
return doc.SelectTokens(RootNode).Select(a => DeserializeRecord(a)).ToList();
}
public TrackingInformations DeserializeRecord(JToken token)
{
string info = string.IsNullOrEmpty(CustomInfo) ? token[Info].Value<string>() : CustomInfo;
string route = string.IsNullOrEmpty(CustomRoute) ? token[Route].Value<string>() : CustomRoute;
return new TrackingInformations()
{
TrackingDate = DateTime.Parse(token[TrackingDate].Value<string>()),
Id = token[Id].Value<string>(),
Info = info,
TrackingId = token[TrackingId].Value<string>(),
Route = route
};
}
}
}