I am writing a method for extracting all properties from an object (including properties of its own) with custom attribute . For example
public class SomeModel
{
[Custom]
public string Name { get; set; }
public string TestData { get; set; }
[Custom]
public string Surname { get; set; }
public InnerModel InnerModel { get; set; }
}
And Inner Model :
public class InnerModel
{
public string Id { get; set; } = "TestID";
[Custom]
public string Year { get; set; }
public ThirdObject HidedObject { get; set; }
}
And the third one :
public class ThirdObject
{
[Custom]
public string HidedName { get; set; }
}
I need to find all properties with "Custom" attribute .
Testing :
SomeModel model = new SomeModel()
{
Name = "farid",
Surname = "Ismayilzada",
TestData = "Test" ,
InnerModel = new InnerModel() { Year ="2022" , HidedObject= New ThirdObject{ HidedName="Secret"}}
};
I need to write the method
GetMyProperties(model) => List<PropInf>()
[PropertyName= Name,Value=Farid ,Route="Name" ]
[PropertyName= Surname,Value=Ismayilzada,Route="Surname" ]
[PropertyName= Year,Value=2022,Route="InnerModel.Year" ]
[PropertyName= HidedName,Value=Secret,Route="InnerModel.HidedObject.HidedName" ]
How to get this information ?
You can write a method like this :
private static IEnumerable<PropInfo> GetPropertiesInfo(object obj, string route = "")
{
List<PropInfo> results = new List<PropInfo>();
// You can filter wich property you want https://learn.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo?view=net-6.0
var objectProperties = obj.GetType().GetProperties().Where(p => p.CanRead);
foreach (var property in objectProperties)
{
var value = property.GetValue(obj);
if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
results.AddRange(GetPropertiesInfo(value, route + property.Name + "."));
}
else
{
// Check if the property has the Custom Attribute
var customAttributes = property.GetCustomAttributes<CustomAttribute>();
if (!customAttributes.Any())
continue;
// You can set a method in your Attribute : customAttributes.First().CheckIfNeedToStoreProperty(obj);
results.Add(new PropInfo()
{
PropertyName = property.Name,
Value = value,
Route = route + property.Name
});
}
}
return results;
}
public class PropInfo
{
public string PropertyName { get; set; }
public object Value { get; set; }
public string Route { get; set; }
}
public class CustomAttribute : Attribute
{
public bool CheckIfNeedToStoreProperty(object obj)
{
return true;
}
}
i am working on source generator and i need to read properties data type as string
Is there any solution to get data type name such as int, string, bool, etc...?
EDIT:
var typeName = property.Type.ToString(); throws exception
EDIT 2:
public void Execute(GeneratorExecutionContext context)
{
var model = new TemplateModel();
var syntaxReceiver = (SolnetParsableClassExplorer)context.SyntaxReceiver;
syntaxReceiver?.SolnetDtos.ForEach(dto =>
{
var typeNodeSymbol = context.Compilation
.GetSemanticModel(dto.SyntaxTree)
.GetDeclaredSymbol(dto);
var dataClass = new DataClassModel();
foreach (var member in dto.DescendantNodes())
{
if (member is PropertyDeclarationSyntax property)
{
dataClass.AddProperty(property);
}
}
dataClass.ClassName = dto.Identifier.ValueText;
model.DataClassModels.Add(dataClass);
});
...
}
internal class SolnetParsableClassExplorer : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> SolnetDtos { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classSyntax &&
classSyntax.HaveAttribute("SolnetDto"))
{
SolnetDtos.Add(classSyntax);
}
}
}
internal class DataClassModel
{
public List<DataField> Properties = new List<DataField>();
public string Namespace { get; set; }
public string ClassName { get; set; }
public int Size => Properties.Sum(p => p.Size);
public void AddProperty(PropertyDeclarationSyntax property)
{
// Here generator throw exception
var typeName = property.Type.ToString();
Properties.Add(
new DataField
{
PropertyName = property.Identifier.ValueText,
DataType = typeName,
Size = GetPropertySize(compilation, property, typeName),
WriteMethod = GetWriteMethod(typeName),
ReadMethod = GetReadMethod(typeName),
Offset = Size
});
}
I hope I understood your question correctly. To retrieve the string representation, you could use the TypeSyntax.ToString()
var strType = propertyDeclarationSyntax.Type.ToString();
For example, for the following
public string Bar1 { get; set; }
public int Bar2 { get; set; }
public long Bar3 { get; set; }
Result
Property Bar1 : string
Property Bar2 : int
Property Bar3 : long
I have this json string and i want to get the 4th line (iValue, sValue) of every record.
My problem here is the keys vary for every record (based on the data type of the value).
Is there any way to do this on C#?
Here is an example:
{ "data": [
{
"pKey": "0",
"Entity": "tableName",
"Attribute": "CID",
"iValue": "13"
},
{
"pKey": "0",
"Entity": "tableName",
"Attribute": "username",
"sValue": "test_user1"
}] }
Here is kind of a big implementation, you will have to implement this for each iValue, fValue, etc however, it speeds up the implementation and usage. First of, here is the usage:
string rawJson = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var values = JsonConvert.DeserializeObject<TakeData>(rawJson).Data.Select(v => v.PureData);
Now values contains the list. Here is the usage for accessing each:
foreach (var val in values)
{
if (val is IntData i)
{
int myInt = i.iValue;
// use the rest of the properties
}
else if (val is StrData s)
{
string myStr = s.sValue;
// use the rest of the properties
}
}
And here is the implementation:
class TakeData
{
public List<TakeItAll> Data { get; set; }
}
class TakeItAll
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
private int _iValue;
public int iValue
{
get => _iValue;
set
{
_iValue = value;
PureData = new IntData { pKey = pKey, Entity = Entity, Attribute = Attribute, iValue = iValue };
}
}
private string _sValue;
public string sValue
{
get => _sValue;
set
{
_sValue = value;
PureData = new StrData { pKey = pKey, Entity = Entity, Attribute = Attribute, sValue = sValue };
}
}
public IPureData PureData { get; private set; }
}
interface IPureData
{
int pKey { get; set; }
string Entity { get; set; }
string Attribute { get; set; }
}
class IntData : IPureData
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public int iValue { get; set; }
}
class StrData : IPureData
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public string sValue { get; set; }
}
Of course you can use some alternatives as well. Such as using an enum in TakeItAll to keep track of the data type (or a type variable) instead of so many classes. This way However the size of the values object would be larger.
class TakeItAll
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
private int _iValue;
public int iValue
{
get => _iValue;
set
{
_iValue = value;
ValType = typeof(string);
}
}
private string _sValue;
public string sValue
{
get => _sValue;
set
{
_sValue = value;
ValType = typeof(int);
}
}
public Type ValType { get; private set; }
}
I would deserialize this into an object supporting both types of properties and then by code try parsing either the integer or the string if the integer fails.
If the Attribute value gives you a clue as to which one to look for, you could also use that to prevent having to try parsing the integer every time.
I would not rely on the property being the "fourth" property every time, as I'm assuming this would be external data, where you may not be able to control whether these properties come out in the exact same order every time (now and in the future).
If you don't know the data type then you could use an object to handle it.
It's a good idea to deserialize JSON string to concrete class to avoid string manipulation mistake.
public class Datum
{
public object pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public string iValue { get; set; }
public string sValue { get; set; }
}
public class DataCollection
{
public List<Datum> data { get; set; }
}
public void Test()
{
var str = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var list = JsonConvert.DeserializeObject<DataCollection>(str);
var keys = list.data.Select(x => x.pKey).ToList();
}
Another option is to deserialize to dynamic and inspect that:
var json = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var result = JsonConvert.DeserializeAnonymousType<dynamic>(json, null);
if (result.data != null)
{
for (var i = 0; i < result.data.Count; i++)
{
if (result.data[i]["iValue"] != null)
// Parse iValue
if (result.data[i]["sValue"] != null)
// Parse sValue
}
}
You could load the Json in a ExpandoObject
var expConverter = new ExpandoObjectConverter();
dynamic objList = JsonConvert.DeserializeObject<List<ExpandoObject>>(json, expConverter);
JSON array to ExpandoObject via JSON.NET
Then once you have loaded it in as a List<ExpandoObject> you may itterate over it as a dictionary.
foreach(var obj in objList)
{
//convert the object to a Dictionary and select the 4th element.
var yourresult = (obj as IDictionary<string, object>).ElementAt(3);
}
My two cents:
Get each object of the array as a KeyValuePair
var json = "your json here";
var root = JsonConvert.DeserializeObject<Root>(json);
foreach (var element in root.Data)
{
//===================================================> Here using object because your value type change. You can change it to string if your value is always wrapped in a string (like "13")
var keyValuePair = element.ToObject<Dictionary<string, object>>();
//here, for each object of the 'data' array, you can check if the desidered property exists
if (keyValuePair.ContainsKey("iValue"))
{
var propertyValue = keyValuePair["iValue"];
}
else if (keyValuePair.ContainsKey("sValue"))
{
var propertyValue = keyValuePair["sValue"];
}
// Or you can check the property name in the desidered position
if (keyValuePair.Keys.ElementAt(3) == "iValue")
{
var propertyValue = keyValuePair["iValue"];
}
else if (keyValuePair.Keys.ElementAt(3) == "sValue")
{
var propertyValue = keyValuePair["sValue"];
}
}
Where Root is
class Root
{
[JsonProperty("data")]
public List<JObject> Data { get; set; }
}
With this solution you can always know which property (iValue or sValue) is specified. On the contrary, if you use a model class which has both property names, you wouldn't know which property is specified when the value is null (unless you use additional properties/classes and a custom JsonConverter).
Edit
As Panagiotis Kanavos reminded me, the JObject class implements IDictionary<string, JToken>. So, inside your foreach you could use:
if (element["iValue"] != null)
{
var propertyValue = element["iValue"].Value<string>();
}
if (element["sValue"] != null)
{
var propertyValue = element["sValue"].Value<string>();
}
// Or you can check the property name in the desidered position
var propName = element.Properties().ElementAt(3).Name;
if (propName == "iValue")
{
var propertyValue = keyValuePair["iValue"].Value<string>();
}
else if (propName == "sValue")
{
var propertyValue = keyValuePair["sValue"].Value<string>();
}
Of course you can optimize this code and check for nulls.
I know I can use reflection to set a objects property like this below.
public void SaveContent(string propertyName, string contentToUpdate, string corePageId)
{
var page = Session.Load<CorePage>(corePageId);
Type type = page.GetType();
PropertyInfo prop = type.GetProperty(propertyName);
prop.SetValue(page, contentToUpdate, null);
}
I'm having these classes below:
public class CorePage
{
public string BigHeader { get; set; }
public List<BigLinks> BigLinks { get; set; }
}
public class BigLinks
{
public string TextContent { get; set; }
}
My SaveContent()-method works obviously when the property to set is, for example public string BigHeader { get; set; }
But how can I do this if the the property I want to set is in the property:
public List<BigLinks> BigLinks { get; set; }
If public List<BigLinks> BigLinks { get; set; } is a list of 5 BigLinks objects, how can a set the value of, for example the third objects public string TextContent { get; set; }?
You have to get the property value using reflection and change the desired value like this:
var c = new CorePage() { BigLinks = new List<BigLinks> { new BigLinks { TextContent = "Y"}}};
var r = typeof(CorePage).GetProperty("BigLinks").GetGetMethod().Invoke(c, null) as List<BigLinks>;
r[0].TextContent = "X";
If you don't know the type of list item:
var itemInList = (typeof(CorePage).GetProperty("BigLinks").GetGetMethod().Invoke(c, null) as IList)[0];
itemInList.GetType().GetProperty("TextContent").SetValue(itemInList, "XXX", null);
Another option is casting to dynamic:
var itemInList = (typeof(CorePage).GetProperty("BigLinks").GetGetMethod().Invoke(c, null) as dynamic)[0].TextContent = "XXXTTT";
I have a DTO that looks like this:
public class MyDto
{
[MyAttribute("attribute1")]
public string Property1 {get;set;}
[MyAttribute("attribute2")]
public string Property2 {get;set;}
}
If I have the string "attribute1", how do I use that to get to the value of Property1 in an instance of MyDto?
Use Reflection. Unfortunately, there's no way to obtain the property from an attribute: you have to iterate through each property and check its attribute.
Not the most robust code, but try this:
public class MyAttributeAttribute : Attribute
{
public MyAttributeAttribute(string value)
{
Value=value;
}
public string Value { get; private set; }
}
public class MyDto
{
[MyAttribute("attribute1")]
public string Property1 { get; set; }
[MyAttribute("attribute2")]
public string Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyDto dto=new MyDto() { Property1="Value1", Property2="Value2" };
string value=GetValueOf<string>(dto, "attribute1");
// value = "Value1"
}
public static T GetValueOf<T>(MyDto dto, string description)
{
if(string.IsNullOrEmpty(description))
{
throw new InvalidOperationException();
}
var props=typeof(MyDto).GetProperties().ToArray();
foreach(var prop in props)
{
var atts=prop.GetCustomAttributes(false);
foreach(var att in atts)
{
if(att is MyAttributeAttribute)
{
string value=(att as MyAttributeAttribute).Value;
if(description.Equals(value))
{
return (T)prop.GetValue(dto, null);
}
}
}
}
return default(T);
}
}