Trying to find property names given deeply nested classes / objects - c#

I'm digging into an object which contains "values" in a string format that correspond to objects and properties nested within the object I get said values from. I.E. If my object contains a list of nested objects that contains, say.. Name, within a Contact object, then I have a "value" that might read something like "ContactInfo[0].Name".
How do I verify the property exists? I'm pretty sure that if I can figure this first part out, then I can worry about getting the value with a lot less difficulty.
I've tried using something along the lines of:
public static bool HasProperty(this object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName) != null;
}
with the arguments "MasterInfo", "Keys[0].KeyWord" and "Keys[1].Keyword" respectively.
I've tried breaking them down by DOT notation into segements and everything else I can think of, but I can't seem to use the keywords in the method successfully. Always False.
I tried running the MasterInfo object through a routine that broke the keyword down by '.' and iterated over each portion, but no change in the result... always False.
Fresh eyes are so very welcome! I've got to be missing something simple and/or right in front of me...
// simplified object example
public class MasterInfo
{
public int Id { get; set; }
public List<ContactInfo> Contacts {get; set;}
public Message MessageDetail { get; set; }
public List<Key> Keys { get; set; }
}
public class ContactInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class Message
{
public int Id { get; set; }
public string MessageContent { get; set; }
}
public class Key
{
public int Id { get; set; }
public string KeyWord { get; set; }
}
// serialized JSON of what the MasterInfo class would be populated with, easier to read than with '.' notation
{
"Id" : 1,
"Contacts" : [
{
"Id" : 1,
"Name" : "Beavis",
"Email" : "beavis#asd.asd"
}
],
"MessageDetail" : {
"Id" : 23,
"MessageContent" : "Hello, %%Contacts[0].Name%%, this was sent to you at %%Contacts[0].Email%%"
},
"Keys" : [
{
"Id" : 1,
"KeyWord" : "Contacts[0].Name"
},
{
"Id" : 2,
"KeyWord" : "Contacts[0].Email"
}
]
}
// method I'm trying to use to verify the keyword (property) exists before attempting to get it's value...
public static bool HasProperty(this object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName) != null;
}
So far, everything turns up false when evaluated. I'm pretty sure this has to do with the fact that I'm diving into nested objects, but at this point I'm not certain of anything.

public static bool HasProperty(this object obj, params string[] properties)
{
return HasProperty(obj.GetType(), properties);
}
public static bool HasProperty(this Type type, params string[] properties)
{
if (properties.Length == 0) // if done properly, shouldn't need this
return false;
var propertyInfo = type.GetProperty(properties[0]);
if (propertyInfo != null)
{
if (properties.Length == 1)
return true;
else // need to check the next level...
{
Type innerType = propertyInfo.PropertyType.GetGenericArguments().FirstOrDefault();
if (innerType != null)
return HasProperty(innerType, properties.Skip(1).ToArray());
else
return false;
}
}
else
return false;
}
public static void Testing()
{
MasterInfo masterInfo = new MasterInfo();
Console.WriteLine(HasProperty(masterInfo, "Id")); // true
Console.WriteLine(HasProperty(masterInfo, "Contacts", "Name")); // true
Console.WriteLine(HasProperty(masterInfo, "Contacts", "Address")); // false
}

Related

Recursive PropertyInformation from Nested IEnumerable<Model>

I am having trouble getting the property Names of the IEnumerable properties in my models. I cant seem to get the Nested IEnumerables from the TModel classes. I have looked into some reflection examples but haven't something quite along these lines.
I am looking to just get the IEnumerable property names for each nested model and send the property name to a list. The actual value is not important.
Any help would be greatly appreciated.
// TModel = DataContent in this context.
public class GetModelBase<TModel>
{
public string Error { get; set; }
public IEnumerable<TModel> DataContent { get; set; }
}
public class DataContent
{
public int Total { get; set; }
public IEnumerable<Data> Data { get; set; }
}
public class Data
{
public int DataId{ get; set; }
IEnumerable<DataInformation> DataInformation{ get; set; }
}
public IEnumerable<GetModelBase<TModel>> ResponseAsList<TModel>()
{
// ResponseBody in this context is a string representation of json of the models above...
var toArray = new ConvertJsonArray<GetModelBase<TModel>>(ResponseBody).ReturnJsonArray();
}
// T = GetModelBase<DataContent> in this context.
public class ConvertJsonArray<T>
{
public ConvertJsonArray(string responseString)
{
_responseString = responseString;
Convert();
}
public void Convert()
{
var result = JObject.Parse(_responseString);
// This is where I am having trouble... I am unable to get the nested IEnumerable names.
Type t = typeof(T);
PropertyInfo[] propertyInformation = t.GetProperties(BindingFlags.Public|BindingFlags.Instance);
List<string> toLists = new List<string>();
foreach (PropertyInfo pi in propertyInformation)
toLists.Add(pi.Name);
// End of Property Information Issuse...
foreach (string s in toLists.ToArray())
{
if (result[s] != null)
{
if (!(result[s] is JArray)) result[s] = new JArray(result[s]);
}
}
_jsonAsArray = result.ToString();
}
public string ReturnJsonArray()
{
return _jsonAsArray;
}
private string _responseString { get; set; }
private string _jsonAsArray { get; set; }
}
The result I am looking for in the above code sample would be a list containing only the IEnumerable names as such { "DataContent", "Data", "DataInformation" }
UPDATE:
I am still having trouble looping through each model. I have a nearly working code example.
// This replaces the Type code in the Convert method...
GetProperties(typeof(T))
private void GetProperties(Type classType)
{
foreach (PropertyInfo property in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.PropertyType.IsGenericType && (property.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
ValuesToList.Add(property.Name);
foreach (Type nestedType in property.PropertyType.GetGenericArguments())
{
GetProperties(nestedType);
}
}
}
}
private List<string> ValuesToList { get; set; }
The results for this yields { "DataContent", "Data" } but fails to get "DataInformation". For some reason the IEnumerables are not hit while in the foreach loop. Additional help would be appreciated.
You already have the PropertyInfo, so you are almost there - all that is left is to recognize which properties are of type IEnumerable<...>, where ... can be an arbitrary type.
For this purpose, check the PropertyType property.
It is a Type instance for which you can check whether it is based upon the generic type definition IEnumerable<T> by means of the GetGenericTypeDefinition method.
That method will throw an exception for non-generic types, so you will also have to check IsGenericType:
if (pi.PropertyType.IsGenericType
&& (pi.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
toLists.Add(pi.Name);
}

Deserialize JSON into a sublevel object

Suppose I have this JSON:
{
"Success": true,
"Records": [
{
"f_EMail": "test#me.com",
"f_FirstName": "firstname",
"f_LastName": "lastname",
"f_Country": null
},
{
"f_EMail": "test2#me.com",
"f_FirstName": "firstname2",
"f_LastName": "lastname2",
"f_Country": null
}
]
}
My class looks like this:
public class Result
{
public bool Success { get; set; }
public IEnumrable<Dictionary<string, string>> Records { get; set; }
}
Everything works as expected. But I would like to write my class a little bit differently and put the values inside Record.Data as shown below. I need to find a way to read and write to this model because some values are well known and I would like to access them more directly.
public class Result
{
public bool Success { get; set; }
public IEnumerable<Record> Records { get; set; }
}
public class Record
{
public Dictionary<string, string> Data { get; set; }
public string Email
{
get
{
return Data[KnownRecordField.Email];
}
}
public string FirstName
{
get
{
return Data[KnownRecordField.FirstName];
}
}
...
}
How can I do that?
If you are willing to declare your dictionary as Dictionary<string, object> instead of Dictionary<string, string> you can take advantage of Json.Net's "Extension Data" feature to handle this.
Mark the dictionary with an [JsonExtensionData] attribute.
Make properties for all of the well-known values and give them [JsonProperty] attributes corresponding to their JSON property names.
The well-known JSON properties will be deserialized into their respective members on the class, while all of the remaining values will go into the dictionary.
public class Record
{
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; }
[JsonProperty("f_EMail")]
public string Email { get; set; }
[JsonProperty("f_FirstName")]
public string FirstName { get; set; }
...
}
Fiddle: https://dotnetfiddle.net/hGZ1V7
If you want all the values to go into the dictionary (not just the unknown ones), you can still use the [JsonExtensionData] attribute, then use separate properties to read and write the well-known values directly from the dictionary. Be sure to mark the properties with [JsonIgnore] to avoid potential conflicts during serialization.
public class Record
{
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; }
[JsonIgnore]
public string Email
{
get { return GetDataValue(KnownRecordField.Email); }
set { Data[KnownRecordField.Email] = value; }
}
[JsonIgnore]
public string FirstName
{
get { return GetDataValue(KnownRecordField.FirstName); }
set { Data[KnownRecordField.FirstName] = value; }
}
// use this method to avoid an exception if the well-known value
// isn't present in the dictionary
private string GetDataValue(string key)
{
object value;
return Data.TryGetValue(key, out value) && value != null ? value.ToString() : null;
}
}
public static class KnownRecordField
{
public static readonly string Email = "f_EMail";
public static readonly string FirstName = "f_FirstName";
}
Fiddle: https://dotnetfiddle.net/I04oMM
you can add JsonProperty attribute in record class.
public class Record
{
public Dictionary<string, string> Data { get; set; }
[JsonProperty(PropertyName = "f_EMail")]
public string Email
{
get;set;
}
...
}

Json.NET JsonConvert.DeserializeObject() return null value

i tried to Deserialize this string :
string _jsonObject = {\"Ad\":{\"Type\":\"Request"\,
\"IdAd\":\"xxx#xxx.com\",
\"Category\":\"cat\",
\"SubCategory\":\"subcat\"},
\"Position\":{\"Latitude\":\"38.255\",
\"Longitude\":\"1.2\",
\"Imei\":\"0123456789\"};
}";
Message _message = JsonConvert.DeserializeObject<Message>(_jsonObject);
Works pretty for "Ad" but not instanciate "Position".
Any idea ?
I forgot to make the properties public. Don't forget to do that...
In the interest of helping others that may be experiencing this issue, or one related to it...
In my case, I had an object with an array of other objects, and one of the reference-type properties on those sub-objects was always null after deserialization. I tried all kinds of things, including downloading the JSON.Net source and stepping through it to find the failure point.
To make a long story short, the problem was, of course, my own. Here is a highly simplified version of my JSON and classes.
JSON
{
"$id": "1",
"RowCount": 10,
"Rows": [{
"$id": 2",
"ItemId": "1",
"ItemName": "Some Item",
"Owner": {
"Name": "John Doe",
"Id": "711D04F5-586F-4FD4-8369-4C00B51DD86F",
// other properties...
},
"OwnerId": "711D04F5-586F-4FD4-8369-4C00B51DD86F"
},
// more rows
]
}
Classes
public class Items
{
public int RowCount { get; set; }
public IEnumerable<Item> Rows { get; set; }
}
public class Item
{
private string ownerId;
public string ItemId { get; set; }
public string ItemName { get; set; }
public Person Owner { get; set; }
public string OwnerId
{
get { return this.ownerId; }
set {
if (value != this.ownerId)
{
this.Owner = null;
}
this.ownerId = value;
}
}
}
public class Person
{
public string Name { get; set; }
public string Id { get; set; }
// other properties
}
What was happening is that, because the Owner property appeared in the JSON prior to the OwnerId property, when the OwnerId property was set, the setter code determined that the current value was not the same as the value being set (since the current value was null), so it set the Owner property to null.
To fix it I also check the value being set against the id of the Owner object as well, and skip setting Owner to null if they are the same.
Admittedly, the cause of my problem may not be the same for everyone, but this is at least a cautionary tale to double-check what is happening when your objects are being initialized during deserialization.
I don't know how you are trying to deserialize, but this should work....
string json = "{\"Ad\":{\"Type\":\"Request\", \"IdAd\":\"xxx#xxx.com\", \"Category\":\"cat\", \"SubCategory\":\"subcat\"},\"Position\":{\"Latitude\":\"38.255\", \"Longitude\":\"1.2\", \"Imei\":\"0123456789\"}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
public class Ad
{
public string Type { get; set; }
public string IdAd { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
}
public class Position
{
public string Latitude { get; set; }
public string Longitude { get; set; }
public string Imei { get; set; }
}
public class RootObject
{
public Ad Ad { get; set; }
public Position Position { get; set; }
}
In my case, my class properties had internal setters and after setting them public the problem solved.
In my case there is a more subtle error. It is easy to add leading or trailing spaces in the json keys by mistake. When that happens, the key is not recognized and attempting to deserialize it sets the value to null.
For example: {" id": 123}
This id field is not recognized because of the leading space " id". To fix it, fix the json to have instead "id".
Make sure the name of array in JSON matches with property name in your class
Illustrating (Look for "Components"):
JSON:
{
"Components": [
{
"Attribute1": "ABC",
"Attribute2": "XYZ"
}
]
}
Class:
public class MyClass
{
public IList<Component> Components { get; set; }
}
Deserialize:
JsonConvert.DeserializeObject<MyClass>(File.ReadAllText(#"ComponentSet.json"))
My problem was that I was including the class name at the beginning of my JSON string. I had copy-pasted from the serialized output of another class that contained the one I wanted to deserialize and I had purposefully included the class name thinking this was the correct JSON string. Once I removed the class name from my JSON string, it deserialized just fine.
This article was helpful in realizing this: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/4d766a28-ff38-477f-8abf-48ed01f74cd2/jsonconvertdeserializeobjectlttgtjsonstring-returning-all-propertieslttgt-as-null?forum=wpdevelop
I did not see this answer here so I am including it hoping that it helps those who made the same silly mistake as me.
I've never had any issues using Newtonsoft.Json, but decided to go with built in json libraries in latest project. Ended up with null result. Turns out the following will fail:
JSON:
{
"myProperty": "abc"
}
CLASS:
public void MyClass
{
public string MyProperty { get; set; }
}
Why does it fail? "myProperty" in json is camel case (starts with lower case letter), while MyProperty in MyClass starts with upper case letter. If you make both cases the same it works. I tried figuring out how to configure case insensitivity for the entire app, but apparently that's not possible to do, so I went back to Newtonsoft.JSON and the problem went away.
In my case, it was because I did not have a public constructor on my class.
This is what my class originally looked like:
public class TreeGroup
{
public string Name { get; set; }
public SiteGroup Group { get; set; }
public List<TreeMimicObject> Children { get; set; }
public TreeGroup(SiteGroup item)
{
// Notice this constructor takes a SiteGroup object and there
// is no default constructor
}
}
so I changed the class from the above to this:
public class TreeGroup
{
public string Name { get; set; }
public SiteGroup Group { get; set; }
public List<TreeMimicObject> Children { get; set; }
public TreeGroup()
{
// Added this default constructor here!!
}
public TreeGroup(SiteGroup item)
{
// ...
}
}
and it worked!
In my case the problem was deserializeobject return null when try to convert null value from json to int.
public class ItemCalcResModel
{
public int cartId;
}
I solved the problem by enable nullable in project:
#nullable enable
public class ItemCalcResModel
{
public int? cartId;
}

Neo4j: invalid arguments in CreateRelationship

I'm experimenting with the Neo4jClient in C# and am stuck at the following error:
The best overloaded method match for 'Neo4jClient.IGraphClient.CreateRelationship(Neo4jClient.NodeReference, GraphDB.PrecedesRelationshipo)' has some invalid arguments.
This error is for the line with the following code:
client.CreateRelationship<Process,PrecedesRelationship>(prevProcess, new PrecedesRelationship(currProcess, new PrecedesData(product, isOptional)));
Here, prevProcess and currProcess are both of type Neo4jClient.NodeReference. Actually, I generate the nodes and store their NodeReference values in a dictionary, so that I can easily look them up. The nodes are created just fine.
Below are my classes:
public class Process
{
public string Name { get; set; }
}
,
public class PrecedesData
{
public string Name { get; set; }
public bool IsOptional { get; set; }
public PrecedesData()
{ }
public PrecedesData(string name)
{
this.Name = name;
this.IsOptional = false;
}
public PrecedesData(string name, bool isOptional)
{
this.Name = name;
this.IsOptional = IsOptional;
}
}
and
public class PrecedesRelationship : Relationship<PrecedesData>, IRelationshipAllowingSourceNode<Process>,
IRelationshipAllowingTargetNode<Process>
{
public static readonly string TypeKey = "PRECEDES";
public PrecedesRelationship(NodeReference targetNode, PrecedesData data)
: base(targetNode, data)
{ }
public override string RelationshipTypeKey
{
get { return TypeKey; }
}
}
When I leave out the types in CreateRelationship I get the error that the compiler cannot infer the types.
I looked at the examples on the Neo4jClient Wiki and I thought I got it right but I seem to be mistaken.
What am I missing here?
You should be using Cypher, as the REST API is increasingly legacy. Really, anything non-Cypher is becoming legacy.
client.Cypher
.Start(new { prevProcess, currProcess })
.CreateUnique("prevProcess-[:PRECEDES {precedes}]->currProcess")
.WithParams(new { precedes = new PrecedesData(product, isOptional) })
.ExecuteWithoutResults();
Then, you don't need any relationship classes either.
Also, if you remove the excess constructors, you can shorten the entire code sample down to just this:
public class PrecedesData
{
public string Name { get; set; }
public bool IsOptional { get; set; }
}
client.Cypher
.Start(new { prevProcess, currProcess })
.CreateUnique("prevProcess-[:PRECEDES {precedes}]->currProcess")
.WithParams(new { precedes = new PrecedesData { Name = product, IsOptional = isOptional } })
.ExecuteWithoutResults();

C# - copying property values from one instance to another, different classes

I have two C# classes that have many of the same properties (by name and type). I want to be able to copy all non-null values from an instance of Defect into an instance of DefectViewModel. I was hoping to do it with reflection, using GetType().GetProperties(). I tried the following:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
PropertyInfo[] defectProperties = defect.GetType().GetProperties();
IEnumerable<string> viewModelPropertyNames =
defectViewModel.GetType().GetProperties().Select(property => property.Name);
IEnumerable<PropertyInfo> propertiesToCopy =
defectProperties.Where(defectProperty =>
viewModelPropertyNames.Contains(defectProperty.Name)
);
foreach (PropertyInfo defectProperty in propertiesToCopy)
{
var defectValue = defectProperty.GetValue(defect, null) as string;
if (null == defectValue)
{
continue;
}
// "System.Reflection.TargetException: Object does not match target type":
defectProperty.SetValue(viewModel, defectValue, null);
}
What would be the best way to do this? Should I maintain separate lists of Defect properties and DefectViewModel properties so that I can do viewModelProperty.SetValue(viewModel, defectValue, null)?
Edit: thanks to both Jordão's and Dave's answers, I chose AutoMapper. DefectViewModel is in a WPF application, so I added the following App constructor:
public App()
{
Mapper.CreateMap<Defect, DefectViewModel>()
.ForMember("PropertyOnlyInViewModel", options => options.Ignore())
.ForMember("AnotherPropertyOnlyInViewModel", options => options.Ignore())
.ForAllMembers(memberConfigExpr =>
memberConfigExpr.Condition(resContext =>
resContext.SourceType.Equals(typeof(string)) &&
!resContext.IsSourceValueNull
)
);
}
Then, instead of all that PropertyInfo business, I just have the following line:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);
Take a look at AutoMapper.
There are frameworks for this, the one I know of is Automapper:
http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
Replace your erroneous line with this:
PropertyInfo targetProperty = defectViewModel.GetType().GetProperty(defectProperty.Name);
targetProperty.SetValue(viewModel, defectValue, null);
Your posted code is attempting to set a Defect-tied property on a DefectViewModel object.
In terms of organizing the code, if you don't want an external library like AutoMapper, you can use a mixin-like scheme to separate the code out like this:
class Program {
static void Main(string[] args) {
var d = new Defect() { Category = "bug", Status = "open" };
var m = new DefectViewModel();
m.CopyPropertiesFrom(d);
Console.WriteLine("{0}, {1}", m.Category, m.Status);
}
}
// compositions
class Defect : MPropertyGettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
class DefectViewModel : MPropertySettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
// quasi-mixins
public interface MPropertyEnumerable { }
public static class PropertyEnumerable {
public static IEnumerable<string> GetProperties(this MPropertyEnumerable self) {
return self.GetType().GetProperties().Select(property => property.Name);
}
}
public interface MPropertyGettable : MPropertyEnumerable { }
public static class PropertyGettable {
public static object GetValue(this MPropertyGettable self, string name) {
return self.GetType().GetProperty(name).GetValue(self, null);
}
}
public interface MPropertySettable : MPropertyEnumerable { }
public static class PropertySettable {
public static void SetValue<T>(this MPropertySettable self, string name, T value) {
self.GetType().GetProperty(name).SetValue(self, value, null);
}
public static void CopyPropertiesFrom(this MPropertySettable self, MPropertyGettable other) {
self.GetProperties().Intersect(other.GetProperties()).ToList().ForEach(
property => self.SetValue(property, other.GetValue(property)));
}
}
This way, all the code to achieve the property-copying is separate from the classes that use it. You just need to reference the mixins in their interface list.
Note that this is not as robust or flexible as AutoMapper, because you might want to copy properties with different names or just some sub-set of the properties. Or it might downright fail if the properties don't provide the necessary getters or setters or their types differ. But, it still might be enough for your purposes.
This is cheap and easy. It makes use of System.Web.Script.Serialization and some extention methods for ease of use:
public static class JSONExts
{
public static string ToJSON(this object o)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Serialize(o);
}
public static List<T> FromJSONToListOf<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<List<T>>(jsonString);
}
public static T FromJSONTo<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<T>(jsonString);
}
public static T1 ConvertViaJSON<T1>(this object o)
{
return o.ToJSON().FromJSONTo<T1>();
}
}
Here's some similiar but different classes:
public class Member
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string PetName { get; set; }
public int PetAge { get; set; }
public bool IsUgly { get; set; }
}
public class MemberV2
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string ChildName { get; set; }
public int ChildAge { get; set; }
public bool IsCute { get; set; }
}
And here's the methods in action:
var memberClass1Obj = new Member {
Name = "Steve Smith",
Age = 25,
IsCitizen = true,
Birthday = DateTime.Now.AddYears(-30),
PetName = "Rosco",
PetAge = 4,
IsUgly = true,
};
string br = "<br /><br />";
Response.Write(memberClass1Obj.ToJSON() + br); // just to show the JSON
var memberClass2Obj = memberClass1Obj.ConvertViaJSON<MemberV2>();
Response.Write(memberClass2Obj.ToJSON()); // valid fields are filled
For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel:
class DefectViewModel
{
public DefectViewModel(Defect source) { ... }
}
And if this is the only class (or one of a few) I would not automate it further but write out the property assignments. Automating it looks nice but there may be more exceptions and special cases than you expect.
Any chance you could have both classes implement an interface that defines the shared properties?

Categories

Resources