I have file which looks like this:
-- Name John Smith, PhD
[20,00] Title : Software Engineer
[20,00] Employee Id : 20307
[20,00] Level : 41
[20,00] Start Date : 04/21/2014
[20,00] Org : Security
Every file contains an entry for just 1 person. I need to extract the name, title and level from this file then create and populate an object of the following class:
public class Person
{
public string Name { get; set; }
public string Title { get; set; }
public string Level { get; set; }
}
One way to do it is I create a list of strings which need to be matched:
List<string> properties = new List<string> { "Name", "Title", "Level" };
Then read the file line by line and try to find a match like:
properties.Any(x => line.Contains(x))
If I find a match, I do some string split and parsing to get the values that I need. But this will involve a lot of manual work. Is there a way I can map the strings to a variable of the class and do this parsing?
What I mean is something like this:
Person person = new Person();
Dictionary<string, Object> FieldToDataMember = new Dictionary<string, Object>()
{
{"Name", person.Name},
{"Title", person.Title},
{"Level", person.Level}
};
Now I read the file line by line, if it matches one of the keys, I do the parsing and it directly updates the value of the corresponding variable. This way, I don't need to first find whether there's a match and then again check which string it matched to be able to put it in the right variable. Is something like this possible?
Appreciate your help. Thanks!
Edit: I would also like to exit the loop (foreach (string line in file)) and stop reading the file further after I find all the properties I'm looking for.
One way to do this using a collection of property name strings is to use reflection to get the property and set the value. This requires extra overhead compared to setting the properties directly, but it is fewer lines of code as you were asking for.
We can use a dictionary or a list of tuples (or a custom class) to map the string in the file with the actual property name (in cases like "Start Date" and StartDate).
Here's an example where I added a public static Person FromFile(string filePath) method, which will take in a file path and return a new Person with properties set from the contents of the file.
It works by first determining if any of the property names in the string array are contained in a file line. If one is, then it uses some logic based on your file sample to get the value for that property, and then uses reflection to set the property value of a Person object:
public class Person
{
public string Name { get; set; }
public string Title { get; set; }
public string Level { get; set; }
public string StartDate { get; set; }
private class FileToPropertyMap
{
public string FileValue { get; }
public string PropertyName { get; }
public bool IsSet { get; set; }
public FileToPropertyMap(string fileValue, string propertyName)
{
FileValue = fileValue;
PropertyName = propertyName;
}
}
public static Person FromFile(string filePath)
{
if (!File.Exists(filePath)) throw new FileNotFoundException(nameof(filePath));
var person = new Person();
var propertyMap = new List<FileToPropertyMap>
{
new FileToPropertyMap("Name", "Name"),
new FileToPropertyMap("Title", "Title"),
new FileToPropertyMap("Level", "Level"),
new FileToPropertyMap("Start Date", "StartDate"),
};
foreach (var line in File.ReadLines(filePath))
{
// Find a match for one of the properties
var match = propertyMap.FirstOrDefault(p => line.Contains(p.FileValue));
if (match == null) continue;
// Get the value of the property from the file line
var value = line.Substring(line.IndexOf(match.FileValue) +
match.FileValue.Length).Trim();
if (value.Contains(':')) value = value.Split(':')[1].Trim();
// Set the property value using reflection
person.GetType().GetProperty(match.PropertyName).SetValue(person, value);
// Mark this property as "IsSet"
match.IsSet = true;
// If we've set all the properties, exit the loop
if (propertyMap.All(p => p.IsSet)) break;
}
return person;
}
}
In use, this would look something like:
Person myPerson = Person.FromFile("#c:\Public\PeopleFiles\JohnSmith.txt");
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.IO;
namespace ConsoleApplication167
{
class Program
{
const string FILENAME = #"c:\temp\test.txt";
static void Main(string[] args)
{
List<Person> people = new List<Person>();
StreamReader reader = new StreamReader(FILENAME);
string line = "";
Person person = null;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
if (line.Length > 0)
{
if (line.StartsWith("-- Name"))
{
person = new Person();
people.Add(person);
person.Name = line.Replace("-- Name", "").Trim();
}
else
{
string pattern = "](?'key'[^:]+):(?'value'.*)";
Match match = Regex.Match(line, pattern);
string key = match.Groups["key"].Value.Trim();
string value = match.Groups["value"].Value.Trim();
switch (key)
{
case "Title" :
person.Title = value;
break;
case "Level":
person.Level = value;
break;
}
}
}
}
}
}
public class Person
{
public string Name { get; set; }
public string Title { get; set; }
public string Level { get; set; }
}
}
Related
I have a need to use a delimited string in order by. EG "Product.Reference".
I seem to be having trouble as the result is ordered the same way it was before the method was called.
For example I have this xUnit test that shows my issue.
The asserts show that the order is still the same.
EDIT:
to be clear, I am not testing Order by, but the method PathToProperty.
This test is for demonstration purposes only.
As you can see from the test I am using reflection in method private static object PathToProperty(object t, string path) So I am assuming I am doing something wrong in there?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
namespace Pip17PFinanceApi.Tests.Workers
{
public class InputViewModel
{
public List<OrderViewModel> OrderViewModels { get; set; }
}
public class OrderViewModel
{
public Product Product { get; set; }
public decimal Price { get; set; }
}
public class Product
{
public string Description { get; set; }
public string Reference { get; set; }
}
public class OrderByWithReflection
{
[Fact]
public void OrderByTest()
{
//Arrrange
var model = new InputViewModel
{
OrderViewModels = new List<OrderViewModel>
{
new OrderViewModel{
Product = new Product
{
Reference = "02"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "03"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "01"
}
},
new OrderViewModel{
Product = new Product
{
Reference = "04"
}
},
}
};
//Act
var query = model.OrderViewModels.OrderBy(t => PathToProperty(t, "Product.Reference"));
var result = query.ToList();
//Assert
Assert.Equal("01", result[0].Product.Reference);
Assert.Equal("02", result[1].Product.Reference);
Assert.Equal("03", result[2].Product.Reference);
Assert.Equal("04", result[3].Product.Reference);
}
private static object PathToProperty(object t, string path)
{
Type currentType = t.GetType();
foreach (string propertyName in path.Split('.'))
{
PropertyInfo property = currentType.GetProperty(propertyName);
t = property;
currentType = property.PropertyType;
}
return t;
}
}
}
Your PathToProperty isn't correct. Think about it's return value - the last time through, you set t = property and then return t. But property is a PropertyInfo, so you are just comparing identical objects in the OrderBy.
I have a similar extension method I use:
public static object GetPropertyPathValue(this object curObject, string propsPath) {
foreach (var propName in propsPath.Split('.'))
curObject = curObject.GetType().GetProperty(propName).GetValue(curObject);
return curObject;
}
If used in place of your PathToProperty method, the OrderBy will work.
var query = model.OrderViewModels.OrderBy(t => t.GetPropertyPathValue("Product.Reference"));
You could update your method to be something like:
private static object PathToProperty(object curObject, string path) {
foreach (string propertyName in path.Split('.')) {
var property = curObject.GetType().GetProperty(propertyName);
curObject = property.GetValue(curObject);
}
return curObject;
}
for the same result.
PS Actually, using some other extension methods and LINQ, my normal method handles properties or fields:
public static object GetPathValue(this object curObject, string memberPath)
=> memberPath.Split('.').Aggregate(curObject, (curObject, memberName) => curObject.GetType().GetPropertyOrField(memberName).GetValue(curObject));
I found this answer here at SO, Get nested property values through reflection C#, though when I run it in my case, it also tries to dump/recurse on e.g. a string's property, like Name, and when, it throws an exception.
My classes look like this
public class MyModels
{
public int Id { get; set; }
public DateTime EditDate { get; set; }
public string EditBy { get; set; }
}
public class Person
{
public string Name { get; set; }
}
public class Organization
{
public Person Person { get; set; }
public Organization()
{
Person = new Person();
}
public string Name { get; set; }
}
public class Company : MyModels
{
public Organization Organization { get; set; }
public Company()
{
Organization = new Organization();
}
public string Description { get; set; }
}
And here's the code from the linked answer
var objtree = "";
void DumpObjectTree(object propValue, int level = 0)
{
if (propValue == null)
return;
var childProps = propValue.GetType().GetProperties();
foreach (var prop in childProps)
{
var name = prop.Name;
var value = prop.GetValue(propValue, null);
// add some left padding to make it look like a tree
objtree += ("".PadLeft(level * 4, ' ') + $"{name} = {value}") + Environment.NewLine;
// call again for the child property
DumpObjectTree(value, level + 1);
}
}
DumpObjectTree(itemData);
What I want is to iterate all the properties and check their value.
When I run the above code sample:
it first finds Organization, and recurse
at 1st level it finds Person, and recurse
at 2nd level if finds Name, and recurse
at 3rd level it throws an exception when it tries to GetValue for Name
If I remove my nested classes, and run it:
it first finds Description, and recurse
at 1st level it throws an exception when it tries to GetValue for Description
How do I make it to not try to dump/recurse on properties of type string, datetime, etc., like e.g. Name, Description?
The exception message says: "Parameter count mismatch."
As a note , the expected output/content in the objtree variable is e.g.
Organization = MyNameSpace.Models.Organization
Person = MyNameSpace.Models.Person
Name = TestName
Name = TestCompany
Description = Some info about the company...
Id = 1
EditDate = 31/08/2019
EditBy = user#domain.com
The reason for the exception is that string has a property named Chars. You normally don't see this property, because it's the indexer used when you do something like char c = myString[0];.
This property obviously needs a paramter (the index), and since you don't provide one, an exception is thrown.
To filter the types you don't want to recurse you need to extend the first line in the method. For example
if (propValue == null) return;
if (propValue.GetType().Assembly != Assembly.GetExecutingAssembly())
return;
This will only recurse through types declared in your assembly. If you want special filtering you need to adjust it.
Your current specification ("of type string, datetime etc") is not specific enough to give an exact solution, but I think the idea is clear.
Note that this won't prevent an exception to be raised if you declare an indexer in your own classes. So a better way might be to check for indexers directly:
foreach (var prop in childProps)
{
if (prop.GetIndexParameters().Any()) continue;
Second note: The current code has another flaw: You should keep track of which types you already dumped and abort the recursion when you come across a type the second time. That's possibly the reason for the exception at DateTime. A DateTime has a Date property, which is - hurray - of type DateTime. And so your objtree string grows infinitly until an OutOfMemoryException or StackOverflowException is thrown.
You need to skip recursion when:
Property is a value type
Property is a string
Property value contains reference to the object from the previous recursion level (ie, ParentObject) so that you don't get a stack overflow exception
Edit: Also when property is a collection type. If you want to get creative, you can have your recursor iterate through each object in the collection and then recurse through those
This PropertyInfo recursor seems to do the trick.
[Flags]
public enum PropertyRecursionOverflowProtectionType
{
SkipSameReference,
SkipSameType
}
public class PropertyRecursionBot
{
public object ParentObject { get; set; }
public object CurrentObject { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public Type ParentType { get; set; }
public int Level { get; set; }
}
public static IEnumerable<PropertyRecursionBot> GetAllProperties(object entity,
PropertyRecursionOverflowProtectionType overflowProtectionType = PropertyRecursionOverflowProtectionType.SkipSameReference)
{
var type = entity.GetType();
var bot = new PropertyRecursionBot { CurrentObject = entity };
IEnumerable<PropertyRecursionBot> GetAllProperties(PropertyRecursionBot innerBot, PropertyInfo[] properties)
{
var currentParentObject = innerBot.ParentObject;
var currentObject = innerBot.CurrentObject;
foreach (var pi in properties)
{
innerBot.PropertyInfo = pi;
var obj = pi.GetValue(currentObject);
innerBot.CurrentObject = obj;
//Return the property and value only if it's a value type or string
if (pi.PropertyType == typeof(string) || !pi.PropertyType.IsClass)
{
yield return innerBot;
continue;
}
//This overflow protection check will prevent stack overflow if your object has bidirectional navigation
else if (innerBot.CurrentObject == null ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameReference) && innerBot.CurrentObject == currentParentObject) ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameType) && innerBot.CurrentObject.GetType() == currentParentObject?.GetType()))
{
continue;
}
innerBot.Level++;
innerBot.ParentObject = currentObject;
foreach (var innerPi in GetAllProperties(innerBot, pi.PropertyType.GetProperties()))
{
yield return innerPi;
}
innerBot.Level--;
innerBot.ParentObject = currentParentObject;
innerBot.CurrentObject = obj;
}
}
foreach (var pi in GetAllProperties(bot, type.GetProperties()))
{
yield return pi;
}
}
Use it like this:
public class RecursionTest
{
public string StringValue { get; set; }
public int IntValue { get; set; }
public RecursionTest Test { get; set; }
public RecursionTest ParentTest { get; set; }
}
var rec1 = new RecursionTest
{
IntValue = 20,
StringValue = Guid.NewGuid().ToString()
};
rec1.Test = new RecursionTest
{
IntValue = 30,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1
};
rec1.Test.Test = new RecursionTest
{
IntValue = 40,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1.Test
};
foreach (var bot in GetAllProperties(rec1, PropertyRecursionOverflowProtectionType.SkipSameReference))
{
Console.WriteLine($"{new string(' ', bot.Level * 2)}{bot.PropertyInfo.Name}: {bot.CurrentObject}");
}
Does anyone know how to convert a string which contains json that has no parameter names into a C# array. My code receives json from RESTApi That looks like this
[["name","surname","street","phone"],
["alex","smith","sky blue way","07747233279"],
["john","patterson","richmond street","07658995465"]]
Every example I have seen here involved parameter names and their json looked like this
[Name:"alex",Surname:"smith",street:"sky blue way",phone:"07747233279"],
[Name:"john",Surname:"patterson",street:"richmond street",phone:"07658995465"]]
I am trying to use JavaScriptSerializer but i don't know how to properly maintain a Class for such type of JSON
This is not quite what the OP is asking (2d array), but my approach to this problem.
It seems that you have a collection of people, so I'd create a Person class like this:
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
public string Street { get; set; }
public string Phone { get; set; }
}
And then a parser class that takes the json string as a parameter, and returns a collection of Person:
public class PersonParser
{
public IEnumerable<Person> Parse(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (string.IsNullOrWhiteSpace(content))
{
yield break;
}
// skip 1st array, which contains the property names
var values = JsonConvert.DeserializeObject<string[][]>(content).Skip(1);
foreach (string[] properties in values)
{
if (properties.Length != 4)
{
// ignore line? thrown exception?
// ...
continue;
}
var person = new Person
{
Name = properties[0],
Surname = properties[1],
Street = properties[2],
Phone = properties[3]
};
yield return person;
}
}
}
Using the code:
string weirdJson = #"[[""name"",""surname"",""street"",""phone""],
[""alex"",""smith"",""sky blue way"",""07747233279""],
[""john"",""patterson"",""richmond street"",""07658995465""]]";
var parser = new PersonParser();
IEnumerable<Person> people = parser.Parse(weirdJson);
foreach (Person person in people)
{
Console.WriteLine($"{person.Name} {person.Surname}");
}
You could do this, which will give you a list of list of string
var input = "[[\"name\", \"surname\", \"street\", \"phone\"],\r\n\t[\"alex\", \"smith\", \"sky blue way\", \"07747233279\"],\r\n\t[\"john\", \"patterson\", \"richmond street\", \"07658995465\"]]";
var results = JsonConvert.DeserializeObject<List<List<string>>>(input);
foreach (var item in results)
Console.WriteLine(string.Join(", ", item));
Output
name, surname, street, phone
alex, smith, sky blue way, 07747233279
john, patterson, richmond street, 07658995465
Full Demo Here
i have this code
public class ParameterOrderInFunction : Attribute
{
public int ParameterOrder { get; set; }
public ParameterOrderInFunction(int parameterOrder)
{
this.ParameterOrder = parameterOrder;
}
}
public interface IGetKeyParameters
{
}
public class Person: IGetKeyParameters
{
[ParameterOrderInFunction(4)]
public string Age { get; set; }
public string Name { get; set; }
[ParameterOrderInFunction(3)]
public string Address { get; set; }
[ParameterOrderInFunction(2)]
public string Language { get; set; }
[ParameterOrderInFunction(1)]
public string City { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Address = "my address";
person.Age = "32";
person.City = "my city";
person.Country = "my country";
Test t = new Test();
string result = t.GetParameter(person);
//string result = person.GetParameter();
Console.ReadKey();
}
}
public class Test
{
public string GetParameter(IGetKeyParameters obj)
{
string[] objectProperties = obj.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ParameterOrderInFunction)))
.Select(p => new
{
Attribute = (ParameterOrderInFunction)Attribute.GetCustomAttribute(p, typeof(ParameterOrderInFunction), true),
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
})
.OrderBy(p => p.Attribute.ParameterOrder)
.Select(p => p.PropertyValue)
.ToArray();
string keyParameters = string.Join(string.Empty, objectProperties);
return keyParameters;
}
}
What i am trying to do is to get properties values as one string with some order .
it work fine if i put the function GetParameter inside the Person class.
however, i want to use the function GetParameter with other class as well,
so i create empty interface.
Now i want that every object that is of type IGetKeyParameters can use the function.
but i am getting exception in the line:
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
You should change loading properties from this (that doesn't have such properties) to parameter object:
PropertyValue = p.GetValue(obj) == null ? string.Empty : p.GetValue(obj).ToString()
You are passing the wrong reference as parameter to the method, you need to pass the object which you used to get the type and properties, so change:
p.GetValue(this) // this means pass current instance of containing class i.e. Test
to:
p.GetValue(obj)
Your statement p.GetValue(this) currenly means to pass the current instance of class Test as parameter which is i am pretty sure not what you want.
in your example code.
I'm new to C# and slowly learning as I go forward.
In a console application I want to be able to type in the name of the property I want to display. The problem I stumble upon is that ReadLine will return a string and I do not know how to turn that string in to a reference to the actual property.
I wrote a simple example to explain what I'm trying to do.
The example will now only type out whatever input it gets twice.
I have tried typeof(Person).GetProperty(property).GetValue().ToString() but all I get is an error message saying that there is no overload for GetValue that takes 0 arguments.
Thanks
Rickard
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace AskingForHelp1
{
class Program
{
static void Main(string[] args)
{
Person p = new Person();
p.FirstName = "Mike";
p.LastName = "Smith";
p.Age = 33;
p.displayInfo(Console.ReadLine());
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public UInt16 Age { get; set; }
public Person()
{
FirstName = "";
LastName = "";
Age = 0;
}
public void displayInfo(string property)
{
Console.WriteLine(property + ": " + property);
Console.ReadKey();
}
}
}
You should use smth like this:
public static object GetPropValue( object src, string propName )
{
return src.GetType( ).GetProperty( propName ).GetValue( src, null );
}
As second parameter is an index:
index
Type: System.Object[]
Optional index values for indexed properties. This value should be null for non-indexed properties.
This will give you what you are looking for.
static void Main(string[] args)
{
Person p = new Person();
p.FirstName = "Mike";
p.LastName = "Smith";
p.Age = 33;
Console.WriteLine("List of properties in the Person class");
foreach (var pInfo in typeof (Person).GetProperties())
{
Console.WriteLine("\t"+ pInfo.Name);
}
Console.WriteLine("Type in name of property for which you want to get the value and press enter.");
var property = Console.ReadLine();
p.displayInfo(property);
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public UInt16 Age { get; set; }
public Person()
{
FirstName = "";
LastName = "";
Age = 0;
}
public void displayInfo(string property)
{
// Note this will throw an exception if property is null
Console.WriteLine(property + ": " + this.GetType().GetProperty(property).GetValue(this, null));
Console.ReadKey();
}
}
GetValue needs an instance of your class to actually get value from. Like this:
typeof(Person).GetProperty(property).GetValue(this).ToString()
// to be used in a non-static method of Person
you need to give the GetValue function the reference of the object that contains the property.
you need to change your displayInfo function:
public void displayInfo(string property, Person p)
then in this function you can call the GetValue function