How do I make this JSON load method generic - c#

I have multiple JSON files with data and multiple classes with properties mapped to fields in the JSON files. I have a generic method for loading the JSON like this:
public static TResult LoadFromJSON<TResult>(string pathToJSON)
{
using (var reader = new StreamReader(pathToJSON))
{
var json = reader.ReadToEnd();
var records = JsonConvert.DeserializeObject<TResult>(json);
return records;
}
}
and then another method that passes in the Class object I am targeting into the above LoadFromJSON
public SortingData GetJSONTestData()
{
var pathJSON = #"..\..\TestData\sortData.json";
var sortCriteriaJSON = TestDataLoader.LoadFromJSON<SortingData>(pathJSON);
return sortCriteriaJSON;
}
what I want to do is make that second method generic, so instead of having SortingData class (in 2 places) and sortData.json file, I can pass in the Class (in both places) and the JSON file dynamically..... so I don't have to duplicate the method over and over just for a different class. So something like this:
public <dynamicObject> GetJSONTestData()
{
var pathJSON = #"..\..\TestData\<dynamic json file>";
var sortCriteriaJSON = TestDataLoader.LoadFromJSON<dynamicObject>(pathJSON);
return sortCriteriaJSON;
}
Not sure how to go about this. Any ideas?

You can use typeof and string concatenation to achieve this, like so:
public T GetJsonTestData<T>()
{
// Say T is SortingData, name here is "SortingData"
var name = typeof(T).Name;
// After this, name is "sortingData"
name = Char.ToLowerInvariant(name[0]) + name.Substring(1);
var path = $#"..\..\TestData\{name}";
return LoadFromJson<T>(path);
}
Then you could call it like so:
// This would read "..\..\TestData\sortingData.json"
var sortingData = GetJsonTestData<SortingData>();
// This would read "..\..\TestData\fooBar.json"
var whatever = GetJsonTestData<FooBar>();

It sounds like you want to be able to map types to file names. You can either resolve the file names by convention, or load the mapping through some configuration.
In the example below the configuration is hard coded, but it wouldn't be much to load it from an appSettings file (demonstrating that is beyond the scope of this question). With the configuration, it's pretty trivial to get the file name from the dictionary and pass it to LoadFromJson.
public class TestDataLoader
{
private readonly string basePath = #"..\..\TestData";
private readonly Dictionary<Type, string> fileNameRegistry = new()
{
{ typeof(SortingData), "sorting_data.json" }
};
public TResult GetJSONTestData<TResult>()
{
var typefileName = fileNameRegistry[typeof(TResult)];
var path = Path.Combine(basePath, typeFileName);
return LoadFromJSON<TResult>(path);
}
public static TResult LoadFromJSON<TResult>(string pathToJSON)
{
using (var reader = new StreamReader(pathToJSON))
{
var json = reader.ReadToEnd();
var records = JsonConvert.DeserializeObject<TResult>(json);
return records;
}
}
}

Related

How to merge a JSON object with the current class

I am trying to make a class that can save and load its own members from a json file automatically.
What I want to know is if NewtonSoft.Json provides a method to do that already or if I will have to use reflection.
class Settings
{
// this is my setting
public bool dostuff = false;
public int maxstuff = 123;
public string namestuff = "foo";
List<string> arrayofstuff = new List<string>();
private string fileLocation;
public Settings(string fileLocation)
{
this.fileLocation = fileLocation;
}
public void LoadSettings()
{
string content = System.IO.File.ReadAllText(this.fileLocation);
JObject data = JObject.Parse(content);
// Normally I would have a sub class that contains all the settings
// I would create an instance of it. Serialize into a JObject
// Then merge with the data object.
// Then use ToObject to assign the updated values
myDuplicateJObject.Merge(data, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
});
// However I need to apply it to the current object which is "this"
}
public void SaveSettings()
{
System.IO.File.WriteAllText(this.fileLocation, JsonConvert.SerializeObject(this));
}
}
The only two ways I can currently think of to solve this would be to use reflection to try and merge a duplicate copy of my class or to make a sub class that contains all the settings and just use that as a member.
You can use JsonConvert.PopulateObject, passing this into it.
public class Settings
{
public string Name = "foo";
public void Populate(string json)
{
JsonConvert.PopulateObject(json, this);
}
}
You can change that Populate method to read the JSON file itself.

Use attributes to make headers more human readable with CSVHelper

I am trying to use CSVHelper to serialize a database that is constructed out of multiple classes like shown below. I would like to make the csv a bit more human readable by adding information on units (when appropriate) and by ordering the data so that the "Name" always appears first. The rest can come in whatever order.
I have a class like shown below.
[DataContract(IsReference = true)]
public class OpaqueMaterial : LibraryComponent
{
[DataMember]
[Units("W/m.K")]
public double Conductivity { get; set; } = 2.4;
[DataMember]
public string Roughness { get; set; } = "Rough";
}
[DataContract]
public abstract class LibraryComponent
{
[DataMember, DefaultValue("No name")]
public string Name { get; set; } = "No name";
}
To avoid writing seprarate read write functions for each class I am reading and writing with templated functions like given below:
public void writeLibCSV<T>(string fp, List<T> records)
{
using (var sw = new StreamWriter(fp))
{
var csv = new CsvWriter(sw);
csv.WriteRecords(records);
}
}
public List<T> readLibCSV<T>(string fp)
{
var records = new List<T>();
using (var sr = new StreamReader(fp))
{
var csv = new CsvReader(sr);
records = csv.GetRecords<T>().ToList();
}
return records;
}
That I then use in the code to read and write as such:
writeLibCSV<OpaqueMaterial>(folderPath + #"\OpaqueMaterial.csv", lib.OpaqueMaterial.ToList());
List<OpaqueMaterial> inOpaqueMaterial = readLibCSV<OpaqueMaterial>(folderPath + #"\OpaqueMaterial.csv");
The CSV output then looks like:
Conductivity, Roughnes, Name
2.4, Rough, No Name
I would like to come out as:
Name, Conductivity [W/m.K], Roughness
No Name, 2.4, Rough
I know that the reordering is possible using maps like:
public class MyClassMap : ClassMap<OpaqueMaterial>
{
public MyClassMap()
{
Map(m => m.Name).Index(0);
AutoMap();
}
}
I would like to make this abstract so that I dont have to apply a different mapping to every class. I was not able to find an example that could help with adding the custom headers. Any suggestions or help would be greatly appreciated.
You could create a generic version of ClassMap<T> that will automatically inspect the type T using reflection and then construct the mapping dynamically based on the properties it finds and based on the attributes that may or may not be attached to it.
Without knowing the CsvHelper library too well, something like this should work:
public class AutoMap<T> : ClassMap<T>
{
public AutoMap()
{
var properties = typeof(T).GetProperties();
// map the name property first
var nameProperty = properties.FirstOrDefault(p => p.Name == "Name");
if (nameProperty != null)
MapProperty(nameProperty).Index(0);
foreach (var prop in properties.Where(p => p != nameProperty))
MapProperty(prop);
}
private MemberMap MapProperty(PropertyInfo pi)
{
var map = Map(typeof(T), pi);
// set name
string name = pi.Name;
var unitsAttribute = pi.GetCustomAttribute<UnitsAttribute>();
if (unitsAttribute != null)
name = $"{name} {unitsAttribute.Unit}";
map.Name(name);
// set default
var defaultValueAttribute = pi.GetCustomAttribute<DefaultValueAttribute>();
if (defaultValueAttribute != null)
map.Default(defaultValueAttribute.Value);
return map;
}
}
Now, you just need to create a AutoMap<T> for every type T that you want to support.
I’ve added examples for a UnitsAttribute and the DefaultValueAttribute, that should give you an idea on how to proceed with more attributes if you need more.

How to return the whole node with all its properties from Neo4J .NET client?

I'm trying to make a generic code that can filter on object connected to an user. These objects can be of different types, with different properties etc.
Basically I want to implement this method:
public string GetRelatedObjects(string sourceObject, string userId){
var resQuery = GraphDB.Cypher
.Match("(src:" + sourceObject + ")--(usr:User { Id:{userId} })")
.WithParam("userId", userId)
.Return(src => src.As<object>());
var result = await resQuery.ResultsAsync;
return JsonConvert.SerializeObject(result);
}
The issue is when I use .As<object>() I get back an empty item.
When I put a concrete type, such as .As<User>() I get back the results I expect. Is there a way to get what I'm trying to get with Neo4JClient or do I have to go lower level somehow?
So under the hood Neo4jClient uses Json.NET to deserialize the output from Neo4j into the class you specify. As you specify object which has no properties you get no properties. Neither Neo4jClient nor Json.NET know what your actually after, so you can only get the object back. Now, solution time - you can use dynamic instead.
public async Task<string> GetRelatedObjectsAsJson(string sourceObject, string userId)
{
var resQuery = GraphDB.Cypher
.Match(string.Format("(src:{0})--(usr:User {{ Id:{{userId}} }})", sourceObject))
.WithParam("userId", userId)
.Return(src => src.As<Node<string>>());
var result = await resQuery.ResultsAsync;
var output = result.Select(node => JsonConvert.DeserializeObject<dynamic>(node.Data)).ToList();
return JsonConvert.SerializeObject(output);
}
I think this will get what you want, if I were you, I would probably return an Task<IEnumerable<dynamic>> instead of reserializing to a string, but I don't know what you want to use the results for, so maybe not appropriate.
Please see Casting nodes of an unknown type for more info.
You can return node with all its properties by creating generalized node class.
Precisely, It can be done by
Creating Generalized "GenNode" Class
Casting your result as GenNode
C# Class :
public class GenNode
{
public long Id { get; set; }
public IEnumerable<String> Labels { get; set; }
public Dictionary<String, String> Properties { get; set; }
}
Rewritten Method :
public IEnumerable<GenNode> GetRelatedObjects(string sourceObject, string userId){
var resQuery = GraphDB.Cypher
.Match("(src:" + sourceObject + ")--(usr:User { Id:{userId} })")
.WithParam("userId", userId)
.With("src, properties(src) as prop")
.Return((src,prop) => new GenNode
{
Id = src.Id(),
Labels = src.Labels(),
Properties = prop.As<Dictionary<Object,Object>>()
});
var result = await resQuery.ResultsAsync;
return result;
}
Pay attention to return type.

C# Expression Serialization / Parameter Replacement

First, a quick overview on what I'm trying to do: I want to take a C# expression, serialize it, send it to another process, de-serialize it, and use it to filter a list. Here's the caveat - when the expression is created it is done so going against a generic parameter type T but when it is de-serialized it needs to go against a dynamic instead. The reason for this is that when it is eventually used on a server in a different process it will be done so against a list of dynamics as I will not have type information in that context.
I feel like I'm close as I've used the Newtonsoft.Json and Serialize.Linq libraries to put together a proof of concept but I can only get it to work without using dynamics (e.g. I have the type T available on both the serializing side (client) and the de-serializing side (server). On to some code to show you what I have...
Given this:
public class Person
{
public string Name { get; set; }
public string Email { get; set; }
}
...as the type we are working with. I have a client that has interface:
public interface IClient
{
T Get<T>(Expression<Func<T, bool>> query);
}
...so that I can do this:
var client = new Client();
var john = client.Get<Person>(p => p.Name == "John");
...all is well and good so far. In the client.Get() method I am taking the passed expression and serializing it down and sending to the server. The server looks as such:
public dynamic Server(string serializedExpression)
{
var people = new List<dynamic>
{
new { Name = "John", Email = "john#stackoverflow.com" },
new { Name = "Jane", Email = "jane#stackoverflow.com" }
};
var serializer = new ExpressionSerializer(new JsonSerializer());
var deserializedExpression = (Expression<Func<dynamic, bool>>)serializer.DeserializeText(serializedExpression);
return people.FirstOrDefault(deserializedExpression.Compile());
}
...and here is where the problems happen because I'm trying to deserialize it into a type of
Expression<Func<dynamic, bool>>
...instead of...
Expression<Func<Person, bool>>.
So my questions are:
1) Is what I'm trying to do even possible? It seems like using an ExpressionVisitor you can change the generic parameter types and I've tried to do so to change from Person to dynamic before I serialize and send but have had no luck.
2) Is there a better way to do what I want to accomplish? I know the first question will be why don't I just get access to the type T specified in the expression Func<> on the server but that won't be possible due to the nature of the server. I basically want the luxury of using Linq on the client to specify query predicates while executing those predicates against dynamic lists.
Thanks in advance for any answers or ideas you can provide.
Regards,
Craig
LINQ doesn't like dynamics in Expressions much. (DLINQ perhaps?)
Alternatively, you could pass along a hint to the server about which object type you're using. I realize this is probably not what you're looking for, but it is working:
(borrowed from this CodeProject article)
public static class Extensions
{
public static object ToType<T>(this object obj, T type)
{
//create instance of T type object:
var tmp = Activator.CreateInstance(Type.GetType(type.ToString()));
//loop through the properties of the object you want to covert:
foreach (PropertyInfo pi in obj.GetType().GetProperties())
{
try
{
//get the value of property and try
//to assign it to the property of T type object:
tmp.GetType().GetProperty(pi.Name).SetValue(tmp,
pi.GetValue(obj, null), null);
}
catch { }
}
//return the T type object:
return tmp;
}
public static object ToNonAnonymousList<T>(this List<T> list, Type t)
{
//define system Type representing List of objects of T type:
var genericType = typeof(List<>).MakeGenericType(t);
//create an object instance of defined type:
var l = Activator.CreateInstance(genericType);
//get method Add from from the list:
MethodInfo addMethod = l.GetType().GetMethod("Add");
//loop through the calling list:
foreach (T item in list)
{
//convert each object of the list into T object
//by calling extension ToType<T>()
//Add this object to newly created list:
addMethod.Invoke(l, new object[] { item.ToType(t) });
}
//return List of T objects:
return l;
}
}
... with some not-so-nice branching on the server:
public interface IClient
{
T Get<T>(Expression<Func<T, bool>> query);
}
public class Person
{
public string Name { get; set; }
public string Email { get; set; }
}
public class Client
{
public T Get<T>(Expression<Func<dynamic, bool>> query)
{
var serializer = new ExpressionSerializer(new JsonSerializer());
var serializedExpression = serializer.SerializeText(query);
return (T)Server.Retrieve(serializedExpression, typeof(T).FullName);
}
}
public static class Server
{
public static dynamic Retrieve(string serializedExpression, string targetType)
{
var people = new List<dynamic>
{
new { Name = "John", Email = "john#stackoverflow.com" },
new { Name = "Jane", Email = "jane#stackoverflow.com" }
};
// Try creating an object of the type hint passed to the server
var typeInstance = Activator.CreateInstance(Type.GetType(targetType));
if (typeInstance.GetType() == typeof(Person))
{
var serializer = new ExpressionSerializer(new JsonSerializer());
var deserializedExpression = (Expression<Func<Person, bool>>)serializer.DeserializeText(serializedExpression);
var peopleCasted = (IEnumerable<Person>)people.ToNonAnonymousList(typeof(Person));
return peopleCasted.Where(deserializedExpression.Compile()).SingleOrDefault();
}
else
{
throw new ArgumentException("Type is unknown");
}
}
}
and a working test:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void MyTestMethod()
{
var client = new Client();
var john = client.Get<Person>(p => p.Name == "John");
Assert.IsNotNull(john);
}
}

Automapper: Ignore types in a collection that have not been set up

Consider the following scenario:
public class DestinationType1 { }
public class DestinationType2 { }
public class SourceType { }
public class SourceTypeA : SourceType { }
public class SourceTypeB : SourceType { }
I set up these mappings:
Mapper.CreateMap<SourceType, DestinationType2>();
Mapper.CreateMap<SourceTypeB, DestinationType2>();
Mapper.CreateMap<SourceType, DestinationType1>();
Mapper.CreateMap<SourceTypeA, DestinationType1>();
Then try to map the following:
var sourceTypes = new List<SourceType>{new SourceTypeA(), new SourceTypeB()};
var destinationType1s = Mapper.Map<List<DestinationType2>>(sourceTypes);
var destinationType2s = Mapper.Map<List<DestinationType1>>(sourceTypes);
What i want to achieve is for destinationType1s to only have one member, mapped from the SourceTypeA in the source list, and destinationType2s to only have one mapped from SourceTypeB. However what i get is two elements in both lists mapped from both the source types.
Is this achievable somehow out of the box or do i need to write my own value resolver or similar?
You could use the OfType LINQ extension method to filter the sourceTypes list.
var sourceTypes = new List<SourceType>{new SourceTypeA(), new SourceTypeB()};
var destinationType1s = Mapper.Map<List<DestinationType1>>(sourceTypes.OfType<SourceTypeA>());
var destinationType2s = Mapper.Map<List<DestinationType2>>(sourceTypes.OfType<SourceTypeB>());
OfType<type> will produce an IEnumerable<type> so you could also remove the 2 maps for the base SourceType if you're not going to use them.
If you want to filter for more than one type, then you could create your own extension method similar to OfType that takes a list of types or takes the DestinationType and looks up which types are mapped to it. Here's something that works using Mapper.FindTypeMapFor to filter only compatible types:
//Mapper.CreateMap<SourceType, DestinationType2>(); -- don't want this!
Mapper.CreateMap<SourceTypeB, DestinationType2>();
//Mapper.CreateMap<SourceType, DestinationType1>(); -- don't want this!
Mapper.CreateMap<SourceTypeA, DestinationType1>();
var sourceTypes = new List<SourceType> { new SourceTypeA(), new SourceTypeB() };
var destinationType1s = Mapper.Map<List<DestinationType1>>(sourceTypes.CompatibleMappedTypes<DestinationType1>());
var destinationType2s = Mapper.Map<List<DestinationType2>>(sourceTypes.CompatibleMappedTypes<DestinationType2>());
...
static class Extensions
{
public static IEnumerable CompatibleMappedTypes<TDestination>(this IEnumerable source)
{
foreach (var s in source)
{
if (Mapper.FindTypeMapFor(s.GetType(), typeof(TDestination)) != null) yield return s;
}
}
}

Categories