Related
I'm having a bit of trouble loading a JSON response from this request into a Deedle DataFrame:https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0/query?where=OBJECTID%3C10&returnGeometry=false&f=json
In the JSON, what I'm interested are the features. More specifically, for each feature there are attributes - I want essentially just a collection of those attributes to load into a DataFrame. In this particular case, there is only one attribute "name" so my expectation is that the resulting DataFrame would have a column "name" with the values shown.
I've tried using json2csharp and creating my own class, but the result either doesn't have the column header/values or the values are missing. I'm not really sure what I'm doing wrong or if I'm even approaching this the right way. My understanding from the Deedle documentation is that it should be possible to create a DataFrame from a collection of objects: https://bluemountaincapital.github.io/Deedle/csharpframe.html#Creating-and-loading-data-frames. Certainly, using the Enumerable example listed on the page works as expected.
Here is the pertinent section of my code:
string url = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0/query?";
WebClient wc = new WebClient();
wc.QueryString.Add("where", "OBJECTID<10");
wc.QueryString.Add("returnGeometry", "false");
wc.QueryString.Add("f", "json");
var data = wc.UploadValues(url, "POST", wc.QueryString);
var responseString = UnicodeEncoding.UTF8.GetString(data);
JObject o = JObject.Parse(responseString);
dynamic x = JsonConvert.DeserializeObject(responseString);
var testObjList = new List<dynamic>();
foreach (dynamic element in x.features)
{
testObjList.Add(new myClass { name = element.attributes.name});
Console.WriteLine($"{element.attributes.name}");
}
var dfObjects = Frame.FromRecords(testObjList);
dfObjects.Print();
var df = Frame.FromRecords(test);
df.Print(); // No headers or values shown
where myClass is just this:
public class myClass{
public string name { get; set; }
}
Any help/pointers would be much appreciated!
The Frame.FromRecords operation relies on static type information to figure out what properties the class has. In your case, you define the list of objects as List<dynamic> - this is compiled as Object and so Deedle does not see any members.
To fix this, all you need to do is to define the type as a list of myClass objects:
var testObjList = new List<myClass>();
A more compact approach using an anonymous type would work too:
var testObjList =
((IEnumerable<dynamic>)x.features).Select(element =>
new { name = element.attributes.name });
var dfObjects = Frame.FromRecords(testObjList);
All of my EF classes have a Projection() method that helps me choose what I want to project from the class to the SQL queries:
Example:
public static Expression<Func<Something, dynamic>> Projection()
{
return e => new
{
something = e.SomethingId,
name = e.Name,
requiredThingId = e.RequiredThingId,
requiredThing = new
{
requiredThingId = e.RequiredThing.RequiredThingId,
name = e.RequiredThing.Name,
...
},
optionalThingId = e.OptionalThingId,
optionalThing = e.OptionalThingId == null ? null : new
{
optionalThingId = e.OptionalThing.OptionalThingId,
name = e.OptionalThing.Name
}
...
};
}
I use these projection methods like this:
List<dynamic> projected = dbContext.Something.Select(Something.Projection()).ToList();
This way lets me reuse my projections all around my project.
I want to use ServiceStack.Text to serialize these lists to CSV.
I'm doing this:
string csvString = CsvSerializer.SerializeToCsv<dynamic>(Something.Select(Something.Projection()).ToList());
byte[] csvBytes = System.Text.Encoding.Unicode.GetBytes(csvString);
return File(csvBytes, "text/csv", "foo.csv");
But the result is an empty csv (csvString is full of "\r\n"'s and nothing more)
Questions:
Am I using correctly the SerializeToCsv() method?
Can I use <dynamic> in this library?
Suggestions for achieving my purpose?
For the example above the desired CSV would flatten all the properties, something like:
"somethingId";"name";"requiredThingId";"requiredThing.requiredThingId";"requiredThing.name";"optionalThingId";"optionalThing.optionalThingId";"optionalThing.name"
I will answer my own questions, but will not mark as accepted in hope of a new greater answer..
Am I using correctly the SerializeToCsv() method? Can I use dynamic
in this library?
Answer: Yes to both but ServiceStack.Text v4.0.36 is needed (which is available at MyGet, not Nuget)
Suggestions for achieving my purpose?
Answer: With ServiceStack.Text it is possible to serialize to CSV a List<dynamic>, but all the nested properties will be rendered as JSON and they will not be flattened out, eg:
List<dynamic> list = new List<dynamic>();
list.Add(new
{
name = "john",
pet = new
{
name = "doggy"
}
});
string csv = CsvSerializer.SerializeToCsv(list);
This list will be serialized to this csv:
name, pet
"john", { name = "doggy" }
And not to this csv, as I was expecting:
name, pet_name
"john", "doggy"
So... I finally ended up writing this code:
public class CsvHelper
{
public static string GetCSVString(List<dynamic> inputList)
{
var outputList = new List<Dictionary<string, object>>();
foreach (var item in inputList)
{
Dictionary<string, object> outputItem = new Dictionary<string, object>();
flatten(item, outputItem, "");
outputList.Add(outputItem);
}
List<string> headers = outputList.SelectMany(t => t.Keys).Distinct().ToList();
string csvString = ";" + string.Join(";", headers.ToArray()) + "\r\n";
foreach (var item in outputList)
{
foreach (string header in headers)
{
if (item.ContainsKey(header) && item[header] != null)
csvString = csvString + ";" + item[header].ToString();
else
csvString = csvString + ";";
}
csvString = csvString + "\r\n";
}
return csvString;
}
private static void flatten(dynamic item, Dictionary<string, object> outputItem, string prefix)
{
if (item == null)
return;
foreach (PropertyInfo propertyInfo in item.GetType().GetProperties())
{
if (!propertyInfo.PropertyType.Name.Contains("AnonymousType"))
outputItem.Add(prefix + "__" + propertyInfo.Name, propertyInfo.GetValue(item));
else
flatten(propertyInfo.GetValue(item), outputItem, (prefix.Equals("") ? propertyInfo.Name : prefix + "__" + propertyInfo.Name));
}
}
}
What this does is:
It flattens the List, so that all the properties of the objects in the list are primitives (eg: no nested properties)
It creates a CSV from that flattened list.
This algorithm is O(n*m), being
n: number of items in the list
m: number of properties inside each item (including nested properties)
Whilst the recommendation is to use clean POCO's for Serialization, you can serialize in-line anonymous types, e.g:
var somethings = dbContext.Something.Select(e => new {
something = e.SomethingId,
name = e.Name
});
somethings.ToCsv().Print();
Otherwise I've just added support for serializing IEnumerable<object> and IEnumerable<dynamic> in this commit.
This change is available from v4.0.36+ that's now available on MyGet.
I believe that part of what is going on is that the library is using properties in the object to determine what to serialize. I believe that a dynamic object does not construct properties like it's a POCO. If you don't setup a getter and setter on your object, it certainly won't work. Just for reference, I using version 4.5.6.0 of this library.
I have a database in Entity Framework that has a set of DTOs created from it that are then consumed by Breeze from the client.
We use the DataAnnotations on the server to validate the data that comes in from Breeze and I want to be able to replicate these validators on the client. Since Breeze implements these validators already and apparently supports adding validators into the metadata I thought I'd give a go at extending the Breeze Server Project.
I am already aware that EDMXWriter only supports a small set of DataAnnotations.
Basically all my project does is add post-generation the required validators into the json that is sent by Breeze.
Here is Part of a 'Table' that has the DataAnnotation of StringLength (That Breeze does support) on the Title Property.
{
"name":"Table",
"customannotation:ClrType":"...",
"key":{
"propertyRef":{
"name":"Id"
}
},
"property":[
{
"name":"Title",
"type":"Edm.String",
"fixedLength":"false",
"unicode":"true",
"validators":[
{
"validatorName":"stringLength",
"maxLength":"Max",
"minLength":1
}
]
}
]
}
I've formatted the output generation to match the requirements set by the scheme on the breeze website: http://www.breezejs.com/documentation/metadata-schema
But Breeze is not interpreting these validators that I am adding to the Metadata.
I noticed that the schema provided by Breeze Server for EF has a different design to the Schema set on the web link above. Does BreezeJS not interpret validators of EF provided Metadata? And if that is the case is there an easy way to enable this or will I have to write that into the client too.
I was aware that the Breeze team did say that they were planning on implementing better EF DataAnnotation support however I've seen nothing come of that. Perhaps this is implemented already and I've missed something? One can only hope it will be that easy.
Regards,
Oliver Baker
There are two Metadata formats that breeze understands. The first, which is the default for an EDM (Entity Framework) based model, is a json serialized version of the EDMX CSDL. This is a MS format which cannot easily be extended, and only supports the limited number of data annotations listed above.
The other alternative is breeze's native metadata format. This format is typically used by any Non Entity Framework based breeze servers. This is also the format used when applying the MetadataStore.exportMetadata and MetadataStore.importMetadata method calls. If your server provides metadata in this format then you can include whatever validations you want. The best way to investigate this format is to simply export the metadata for your current application and take a look. The result is simply the stringified native metadata json.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server ( in your case with some added validators) and simply returning this prestored metadata to the client in production during the Metadata call.
In addition, you can also extend the breeze metadata format: See:
http://www.breezejs.com/documentation/custom-metadata
We have a number of developers who use such extended metadata for a variety of purposes, including the addition of validation metadata.
It seems the EFContextProvider has very limited validation annotation support, basically just:
required - if !isNullable
maxLength - if maxLength is specified
The output listed at http://www.breezejs.com/documentation/metadata-schema is of the metadata object in the client library, once processed.
http://www.breezejs.com/documentation/validation shows how to manually edit this information, and notes the following:
Many of these validators correlate to .NET data annotations . In a future release, the Breeze.NET EFContextProviderwill be able to include these validations in the metadata automatically for you. For now, you'll have to add them to the properties on the client side as we show next.
So if you extend the EFContextProvider with additional metadata, you'll have to manually process this and add it to the validators objects in the property info in the metadata store.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server
Here is my solution using jint, up on github. Obviously computationally expensive, so the method calling this has a [Conditional["DEBUG"]] attribute
public static class MedSimDtoMetadata
{
const string breezeJsPath = #"C:\Users\OEM\Documents\Visual Studio 2015\Projects\SimManager\SM.Web\Scripts\breeze.min.js";
public static string GetBreezeMetadata(bool pretty = false)
{
var engine = new Engine().Execute("var setInterval;var setTimeout = setInterval = function(){}"); //if using an engine like V8.NET, would not be required - not part of DOM spec
engine.Execute(File.ReadAllText(breezeJsPath));
engine.Execute("breeze.NamingConvention.camelCase.setAsDefault();" + //mirror here what you are doing in the client side code
"var edmxMetadataStore = new breeze.MetadataStore();" +
"edmxMetadataStore.importMetadata(" + MedSimDtoRepository.GetEdmxMetadata() + ");" +
"edmxMetadataStore.exportMetadata();");
var exportedMeta = JObject.Parse(engine.GetCompletionValue().AsString());
AddValidators(exportedMeta);
return exportedMeta.ToString(pretty ? Formatting.Indented : Formatting.None);
}
//http://stackoverflow.com/questions/26570638/how-to-add-extend-breeze-entity-types-with-metadata-pulled-from-property-attribu
static void AddValidators(JObject metadata)
{
Assembly thisAssembly = typeof(ParticipantDto).Assembly; //any type in the assembly containing the Breeze entities.
var attrValDict = GetValDictionary();
var unaccountedVals = new HashSet<string>();
foreach (var breezeEntityType in metadata["structuralTypes"])
{
string shortEntityName = breezeEntityType["shortName"].ToString();
string typeName = breezeEntityType["namespace"].ToString() + '.' + shortEntityName;
Type entityType = thisAssembly.GetType(typeName, true);
Type metaTypeFromAttr = ((MetadataTypeAttribute)entityType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).Single()).MetadataClassType;
foreach (var breezePropertyInfo in breezeEntityType["dataProperties"])
{
string propName = breezePropertyInfo["name"].ToString();
propName = char.ToUpper(propName[0]) + propName.Substring(1); //IF client using breeze.NamingConvention.camelCase & server using PascalCase
var propInfo = metaTypeFromAttr.GetProperty(propName);
if (propInfo == null)
{
Debug.WriteLine("No metadata property attributes available for " + breezePropertyInfo["dataType"] + " "+ shortEntityName +'.' + propName);
continue;
}
var validators = breezePropertyInfo["validators"].Select(bp => bp.ToObject<Dictionary<string, object>>()).ToDictionary(key => (string)key["name"]);
//usingMetaProps purely on property name - could also use the DTO object itself
//if metadataType not found, or in reality search the entity framework entity
//for properties with the same name (that is certainly how I am mapping)
foreach (Attribute attr in propInfo.GetCustomAttributes())
{
Type t = attr.GetType();
if (t.Namespace == "System.ComponentModel.DataAnnotations.Schema") {
continue;
}
Func<Attribute, Dictionary<string,object>> getVal;
if (attrValDict.TryGetValue(t, out getVal))
{
var validatorsFromAttr = getVal(attr);
if (validatorsFromAttr != null)
{
string jsValidatorName = (string)validatorsFromAttr["name"];
if (jsValidatorName == "stringLength")
{
validators.Remove("maxLength");
}
Dictionary<string, object> existingVals;
if (validators.TryGetValue(jsValidatorName, out existingVals))
{
existingVals.AddOrOverwrite(validatorsFromAttr);
}
else
{
validators.Add(jsValidatorName, validatorsFromAttr);
}
}
}
else
{
unaccountedVals.Add(t.FullName);
}
}
breezePropertyInfo["validators"] = JToken.FromObject(validators.Values);
}
}
foreach (var u in unaccountedVals)
{
Debug.WriteLine("unaccounted attribute:" + u);
}
}
static Dictionary<Type, Func<Attribute, Dictionary<string, object>>> GetValDictionary()
{
var ignore = new Func<Attribute, Dictionary<string, object>>(x => null);
return new Dictionary<Type, Func<Attribute, Dictionary<string, object>>>
{
[typeof(RequiredAttribute)] = x => new Dictionary<string, object>
{
["name"] = "required",
["allowEmptyStrings"] = ((RequiredAttribute)x).AllowEmptyStrings
//["message"] = ((RequiredAttribute)x).ErrorMessage
},
[typeof(EmailAddressAttribute)] = x => new Dictionary<string, object>
{
["name"] = "emailAddress",
},
[typeof(PhoneAttribute)] = x => new Dictionary<string, object>
{
["name"] = "phone",
},
[typeof(RegularExpressionAttribute)] = x => new Dictionary<string, object>
{
["name"] = "regularExpression",
["expression"] = ((RegularExpressionAttribute)x).Pattern
},
[typeof(StringLengthAttribute)] = x => {
var sl = (StringLengthAttribute)x;
return GetStrLenDictionary(sl.MaximumLength, sl.MinimumLength);
},
[typeof(MaxLengthAttribute)] = x => GetStrLenDictionary(((MaxLengthAttribute)x).Length),
[typeof(UrlAttribute)] = x => new Dictionary<string, object>
{
["name"] = "url",
},
[typeof(CreditCardAttribute)] = x=> new Dictionary<string, object>
{
["name"] = "creditCard",
},
[typeof(FixedLengthAttribute)] = x => //note this is one of my attributes to force fixed length
{
var len = ((FixedLengthAttribute)x).Length;
return GetStrLenDictionary(len, len);
},
[typeof(RangeAttribute)] = x => {
var ra = (RangeAttribute)x;
return new Dictionary<string, object>
{
["name"] = "range",
["min"] = ra.Minimum,
["max"] = ra.Maximum
};
},
[typeof(KeyAttribute)] = ignore
};
}
static Dictionary<string,object> GetStrLenDictionary(int maxLength, int minLength = 0)
{
if (minLength == 0)
{
return new Dictionary<string, object>
{
["name"] = "maxLength",
["maxLength"] = maxLength
};
}
return new Dictionary<string, object>
{
["name"] = "stringLength",
["minLength"] = minLength,
["maxLength"] = maxLength
};
}
static void AddOrOverwrite<K,V>(this Dictionary<K,V> oldValues, Dictionary<K,V> newValues)
{
foreach (KeyValuePair<K,V> kv in newValues)
{
if (oldValues.ContainsKey(kv.Key))
{
oldValues[kv.Key] = kv.Value;
}
else
{
oldValues.Add(kv.Key, kv.Value);
}
}
}
}
I have a class as follows :
public class Test
{
public int Id {get;set;}
public string Name { get; set; }
public string CreatedDate {get;set;}
public string DueDate { get; set; }
public string ReferenceNo { get; set; }
public string Parent { get; set; }
}
and I have a list of Test objects
List<Test>testobjs=new List();
Now I would like to convert it into csv in following format:
"1,John Grisham,9/5/2014,9/5/2014,1356,0\n2,Stephen King,9/3/2014,9/9/2014,1367,0\n3,The Rainmaker,4/9/2014,18/9/2014,1";
I searched for "Converting list to csv c#" and I got solutions as follows:
string.Join(",", list.Select(n => n.ToString()).ToArray())
But this will not put the \n as needed i.e for each object
Is there any fastest way other than string building to do this? Please help...
Use servicestack.text
Install-Package ServiceStack.Text
and then use the string extension methods ToCsv(T)/FromCsv()
Examples:
https://github.com/ServiceStack/ServiceStack.Text
Update:
Servicestack.Text is now free also in v4 which used to be commercial. No need to specify the version anymore! Happy serializing!
Because speed was mentioned in the question, my interest was piqued on just what the relative performances might be, and just how fast I could get it.
I know that StringBuilder was excluded, but it still felt like probably the fastest, and StreamWriter has of course the advantage of writing to either a MemoryStream or directly to a file, which makes it versatile.
So I knocked up a quick test.
I built a list half a million objects identical to yours.
Then I serialized with CsvSerializer, and with two hand-rolled tight versions, one using a StreamWriter to a MemoryStream and the other using a StringBuilder.
The hand rolled code was coded to cope with quotes but nothing more sophisticated. This code was pretty tight with the minimum I could manage of intermediate strings, no concatenation... but not production and certainly no points for style or flexibility.
But the output was identical in all three methods.
The timings were interesting:
Serializing half a million objects, five runs with each method, all times to the nearest whole mS:
StringBuilder 703 734 828 671 718 Avge= 730.8
MemoryStream 812 937 874 890 906 Avge= 883.8
CsvSerializer 1,734 1,469 1,719 1,593 1,578 Avge= 1,618.6
This was on a high end i7 with plenty of RAM.
Other things being equal, I would always use the library.
But if a 2:1 performance difference became critical, or if RAM or other issues turned out to exaggerate the difference on a larger dataset, or if the data were arriving in chunks and was to be sent straight to disk, I might just be tempted...
Just in case anyone's interested, the core of the code (for the StringBuilder version) was
private void writeProperty(StringBuilder sb, string value, bool first, bool last)
{
if (! value.Contains('\"'))
{
if (!first)
sb.Append(',');
sb.Append(value);
if (last)
sb.AppendLine();
}
else
{
if (!first)
sb.Append(",\"");
else
sb.Append('\"');
sb.Append(value.Replace("\"", "\"\""));
if (last)
sb.AppendLine("\"");
else
sb.Append('\"');
}
}
private void writeItem(StringBuilder sb, Test item)
{
writeProperty(sb, item.Id.ToString(), true, false);
writeProperty(sb, item.Name, false, false);
writeProperty(sb, item.CreatedDate, false, false);
writeProperty(sb, item.DueDate, false, false);
writeProperty(sb, item.ReferenceNo, false, false);
writeProperty(sb, item.Parent, false, true);
}
If you don't want to load library's than you can create the following method:
private void SaveToCsv<T>(List<T> reportData, string path)
{
var lines = new List<string>();
IEnumerable<PropertyDescriptor> props = TypeDescriptor.GetProperties(typeof(T)).OfType<PropertyDescriptor>();
var header = string.Join(",", props.ToList().Select(x => x.Name));
lines.Add(header);
var valueLines = reportData.Select(row => string.Join(",", header.Split(',').Select(a => row.GetType().GetProperty(a).GetValue(row, null))));
lines.AddRange(valueLines);
File.WriteAllLines(path, lines.ToArray());
}
and than call the method:
SaveToCsv(testobjs, "C:/PathYouLike/FileYouLike.csv")
Your best option would be to use an existing library. It saves you the hassle of figuring it out yourself and it will probably deal with escaping special characters, adding header lines etc.
You could use the CSVSerializer from ServiceStack. But there are several other in nuget.
Creating the CSV will then be as easy as string csv = CsvSerializer.SerializeToCsv(testobjs);
You could use the FileHelpers library to convert a List of objects to CSV.
Consider the given object, add the DelimitedRecord Attribute to it.
[DelimitedRecord(",")]
public class Test
{
public int Id {get;set;}
public string Name { get; set; }
public string CreatedDate {get;set;}
public string DueDate { get; set; }
public string ReferenceNo { get; set; }
public string Parent { get; set; }
}
Once the List is populated, (as per question it is testobjs)
var engine = new FileHelperEngine<Test>();
engine.HeaderText = engine.GetFileHeader();
string dirPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + ConfigurationManager.AppSettings["MyPath"];
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
//File location, where the .csv goes and gets stored.
string filePath = Path.Combine(dirPath, "MyTestFile_" + ".csv");
engine.WriteFile(filePath, testobjs);
This will just do the job for you. I'd been using this to generate data reports for a while until I switched to Python.
PS: Too late to answer but hope this helps somebody.
Use Cinchoo ETL
Install-Package ChoETL
or
Install-Package ChoETL.NETStandard
Sample shows how to use it
List<Test> list = new List<Test>();
list.Add(new Test { Id = 1, Name = "Tom" });
list.Add(new Test { Id = 2, Name = "Mark" });
using (var w = new ChoCSVWriter<Test>(Console.Out)
.WithFirstLineHeader()
)
{
w.Write(list);
}
Output CSV:
Id,Name,CreatedDate,DueDate,ReferenceNo,Parent
1,Tom,,,,
2,Mark,,,,
For more information, go to github
https://github.com/Cinchoo/ChoETL
Sample fiddle: https://dotnetfiddle.net/M7v7Hi
LINQtoCSV is the fastest and lightest I've found and is available on GitHub. Lets you specify options via property attributes.
Necromancing this one a bit; ran into the exact same scenario as above, went down the road of using FastMember so we didn't have to adjust the code every time we added a property to the class:
[HttpGet]
public FileResult GetCSVOfList()
{
// Get your list
IEnumerable<MyObject> myObjects =_service.GetMyObject();
//Get the type properties
var myObjectType = TypeAccessor.Create(typeof(MyObject));
var myObjectProperties = myObjectType.GetMembers().Select(x => x.Name);
//Set the first row as your property names
var csvFile = string.Join(',', myObjectProperties);
foreach(var myObject in myObjects)
{
// Use ObjectAccessor in order to maintain column parity
var currentMyObject = ObjectAccessor.Create(myObject);
var csvRow = Environment.NewLine;
foreach (var myObjectProperty in myObjectProperties)
{
csvRow += $"{currentMyObject[myObjectProperty]},";
}
csvRow.TrimEnd(',');
csvFile += csvRow;
}
return File(Encoding.ASCII.GetBytes(csvFile), "text/csv", "MyObjects.csv");
}
Should yield a CSV with the first row being the names of the fields, and rows following. Now... to read in a csv and create it back into a list of objects...
Note: example is in ASP.NET Core MVC, but should be very similar to .NET framework. Also had considered ServiceStack.Text but the license was not easy to follow.
For the best solution, you can read this article: Convert List of Object to CSV File C# - Codingvila
using Codingvila.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace Codingvila.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
CodingvilaEntities entities = new CodingvilaEntities();
var lstStudents = (from Student in entities.Students
select Student);
return View(lstStudents);
}
[HttpPost]
public FileResult ExportToCSV()
{
#region Get list of Students from Database
CodingvilaEntities entities = new CodingvilaEntities();
List<object> lstStudents = (from Student in entities.Students.ToList()
select new[] { Student.RollNo.ToString(),
Student.EnrollmentNo,
Student.Name,
Student.Branch,
Student.University
}).ToList<object>();
#endregion
#region Create Name of Columns
var names = typeof(Student).GetProperties()
.Select(property => property.Name)
.ToArray();
lstStudents.Insert(0, names.Where(x => x != names[0]).ToArray());
#endregion
#region Generate CSV
StringBuilder sb = new StringBuilder();
foreach (var item in lstStudents)
{
string[] arrStudents = (string[])item;
foreach (var data in arrStudents)
{
//Append data with comma(,) separator.
sb.Append(data + ',');
}
//Append new line character.
sb.Append("\r\n");
}
#endregion
#region Download CSV
return File(Encoding.ASCII.GetBytes(sb.ToString()), "text/csv", "Students.csv");
#endregion
}
}
}
Are there any libraries in .Net to help compare and find differences between two json objects? I've found some solutions available for JavaScript, but nothing interesting for C#. The point of my question is to create json with changes marked in some way, based on the comparison. So that the user could see where the changes are.
using Microsoft.XmlDiffPatch;
using Newtonsoft.Json;
Convert each json to xml and use MS XmlDiff libary. Available on nuget. Differences are given in another xml doc which here I write to the console. This is suitable for unit testing for example.
public bool CompareJson(string expected, string actual)
{
var expectedDoc = JsonConvert.DeserializeXmlNode(expected, "root");
var actualDoc = JsonConvert.DeserializeXmlNode(actual, "root");
var diff = new XmlDiff(XmlDiffOptions.IgnoreWhitespace |
XmlDiffOptions.IgnoreChildOrder);
using (var ms = new MemoryStream())
using (var writer = new XmlTextWriter(ms, Encoding.UTF8))
{
var result = diff.Compare(expectedDoc, actualDoc, writer);
if (!result)
{
ms.Seek(0, SeekOrigin.Begin);
Console.WriteLine(new StreamReader(ms).ReadToEnd());
}
return result;
}
}
I have used different JSON objects than those in your example but it will apply to your case correctly.
private static string GetJsonDiff(string action, string existing, string modified, string objectType)
{
// convert JSON to object
JObject xptJson = JObject.Parse(modified);
JObject actualJson = JObject.Parse(existing);
// read properties
var xptProps = xptJson.Properties().ToList();
var actProps = actualJson.Properties().ToList();
// find differing properties
var auditLog = (from existingProp in actProps
from modifiedProp in xptProps
where modifiedProp.Path.Equals(existingProp.Path)
where !modifiedProp.Value.ToString().Equals(existingProp.Value.ToString())
select new AuditLog
{
Field = existingProp.Path,
OldValue = existingProp.Value.ToString(),
NewValue = modifiedProp.Value.ToString(),
Action = action, ActionBy = GetUserName(),
ActionDate = DateTime.UtcNow.ToLongDateString(),
ObjectType = objectType
}).ToList();
return JsonConvert.SerializeObject(auditLog);
}
I think your best bet is to use JSON.NET to create two JSON objects, then recursively loop through the tree, comparing each node to see if it exists and is equal while you go.
I think the best way to go here is to create objects using newtonsoft json.http://www.nuget.org/packages/newtonsoft.json/
So, you will have two objects of the same type, which you can easily compare and mark the differences.
private IEnumerable<JProperty> JSONCompare(string expectedJSON, string actualJSON)
{
// convert JSON to object
JObject xptJson = JObject.Parse(expectedJSON);
JObject actualJson = JObject.Parse(actualJSON);
// read properties
var xptProps = xptJson.Properties().ToList();
var actProps = actualJson.Properties().ToList();
// find missing properties
var missingProps = xptProps.Where(expected => actProps.Where(actual => actual.Name == expected.Name).Count() == 0);
return missingProps;
}