Assuming the following model
public class MyObject
{
public string Name { get; set; }
public ICollection<MyObjectItem> Items { get; set; }
}
public class MyObjectItem
{
public string Name { get; set; }
public int Total { get; set; }
}
I want to serialize and deserialize this object graph to a list of key/value pair of strings like :
MyObject.Name - "Name"
MyObject.Items.0.Name - "Name1"
MyObject.Items.0.Total - "10"
MyObject.Items.1.Name - "Name2"
MyObject.Items.1.Total - "20"
Object serialization is usually expensive for big xml structures. If it's possible, try to use XMlWriter or XmlTextWriter - usage example: http://dotnetperls.com/xmlwriter
Well, you could not use a built-in serializer for that, you would need a custom ToString() / Parse(), similar to this: (ToString() is kind of self explanatory)
MyObject obj = new MyObject();
List<MyObjectItem> items = new List<MyObjectItem>();
foreach (string line in text.Split)
{
// skip MyObject declaration int idx = line.IndexOf('.');
string sub = line.Substring(idx);
if (sub.StartsWith("Name")) {
obj.Name = sub.Substring("Name".Length + 3 /* (3 for the ' - ' part) */);
}
else
{
sub = sub.Substring("Items.".Length);
int num = int.Parse(sub.Substring(0, sub.IndexOf('.'));
sub = sub.Substring(sub.IndexOf('.' + 1);
if (items.Count < num)
items.Add(new MyObjectItem());
if (sub.StartsWith("Name"))
{
items[num].Name = sub.SubString("Name".Length + 3);
}
else
{
items[num].Total = sub.SubString("Total".Length + 3);
}
}
}
obj.Items = items;
Hope this helps, as I do not have access to a C# IDE at this time...
Related
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}");
}
I am trying to update a List which is a List of Interfaces to concrete classes.
I add to the List each Market type i am interested in, for this Example these Markets are A and B
I loop over all the markets, (sample provided with 3 markets A B & C, we are only interested in A and B) And determine which is of interest to us.
Once found we pass this to an extraction method too do its work and create an instance of the Correct Market_ class type.
This all works fine, but when i try to update the list with the Updates it does not get reflected in the List.
Code below, any Suggestions?
Thanks
public class Test
{
public Test()
{
TheMarkets MarketsToUpdate = new TheMarkets();
List<SpecificCompanyMarket> lstMarks = new List<SpecificCompanyMarket>();
lstMarks.Add(new SpecificCompanyMarket(1234, "A", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(5874, "B", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(2224, "C", "Some HTML DATA HERE"));
foreach (var item in lstMarks)
{
if (MarketsToUpdate.IsMarketWeAreInterestedIn(item.MarketName))
{
ITheMarkets MarkToUpdate = ExtractMarketData(item);
var obj = MarketsToUpdate.MarketsWeAreInterestedIn.FirstOrDefault(x => x.MarketName() == "A");
if (obj != null)
{
obj = MarkToUpdate;
}
}
}
//Look At MarketsToUpdate Now and the item has not changed, still original values
//I was expecting to see the new values for the fields in A, not the default 0's
}
public ITheMarkets ExtractMarketData(SpecificCompanyMarket item)
{
ITheMarkets market = null;
if (item.MarketName.ToUpper() == "A")
{
Market_A marketType = new Market_A();
marketType.SomeValue1 = 123;
marketType.SomeValue2 = 158253;
market = marketType;
}
//Other Market extractions here
return market;
}
}
public class SpecificCompanyMarket
{
public int MarketId { get; set; }
public string MarketName { get; set; }
public string MarketDataHTML { get; set; }
public SpecificCompanyMarket(int MID, string MName, string MData)
{
MarketId = MID;
MarketName = MName;
MarketDataHTML = MData;
}
}
public class TheMarkets
{
public List<ITheMarkets> MarketsWeAreInterestedIn = new List<ITheMarkets>();
public TheMarkets()
{
Market_A A = new Market_A();
Market_B B = new Market_B();
MarketsWeAreInterestedIn.Add(A);
MarketsWeAreInterestedIn.Add(B);
}
public bool IsMarketWeAreInterestedIn(string strMarketName)
{
bool blnRetVal = false;
foreach (var item in MarketsWeAreInterestedIn)
{
if (item.MarketName().ToUpper().Trim().Equals(strMarketName.ToUpper().Trim()))
{
blnRetVal = true;
break;
}
}
return blnRetVal;
}
}
public interface ITheMarkets
{
string MarketName();
}
public class Market_A : ITheMarkets
{
public string LabelType { get; private set; }
public double SomeValue1 { get; set; }
public double SomeValue2 { get; set; }
public double SomeValue3 { get; set; }
public Market_A()
{
LabelType = "A";
}
public string MarketName()
{
return LabelType;
}
}
public class Market_B : ITheMarkets
{
public string LabelType { get; private set; }
public List<string> SomeList { get; set; }
public double SomeValue { get; set; }
public Market_B()
{
LabelType = "B";
}
public string MarketName()
{
return LabelType;
}
}
This is a short example to get you going. Loop through your list, find the object you want to update, create a new object of that type and then find the original objects index in the list and overwrite it in place. You are essentially just replacing the object in the list with a new one not mutating the existing one.
foreach (var item in lstMarks)
{
//your code to get an object with data to update
var yourObjectToUpdate = item.GetTheOneYouWant();
//make updates
yourObjectToUpdate.SomeProperty = "New Value";
int index = lstMarks.IndexOf(item);
lstMarks[index] = yourObjectToUpdate;
}
You are extracting an obj from marketWeAreInterestedIn list using LINQ's firstOrDefault extension. This is a new object and not a reference to the obj in that list. Therefore, no updates will be reflected in the object inside that list. Try using 'indexof'
You are not storing "list of interfaces" in your list. List<T> stores an array of pointers to objects that support T interface. Once you enumerate (with Linq in your case) your list, you copy a pointer from list, which is not associated with list itself in any way. It is just a pointer to your instance.
To do what you want, you will have to build new list while enumerating the original one, adding objects to it, according to your needs, so the second list will be based on the first one but with changes applied that you need.
You can also replace specific instance at specific index instead of building new list in your code, but to do this you will need to enumerate your list with for loop and know an index for each item:
list[index] = newvalue;
But there is a third solution to update list item directly by Proxying them. This is an example
class ItemProxy : T { public T Value { get; set; } }
var list = new List<ItemProxy<MyClass>>();
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
foreach(var item in list)
if(item // ...)
item.Value = new MyClass(); // done, pointer in the list is updated.
Third is the best case for perfomance, but it will be better to use this proxying class for something more than just proxying.
This is the class I am trying to serialize and deserialize:
[Serializable]
[XmlRoot("RecipeStepWords")]
public class CRecipeStepsWords
{
[XmlArray]
public List<RecipeStep> ReadRecipeSteps { get; set; }
[XmlArray]
public List<RecipeStep> WriteRecipeSteps { get; set; }
public CRecipeStepsWords()
{
ReadRecipeSteps = new List<RecipeStep>();
for (int x = 1; x <= 8; x++)
{
ReadRecipeSteps.Add(new RecipeStep());
}
WriteRecipeSteps = new List<RecipeStep>();
for (int x = 1; x <= 6; x++)
{
WriteRecipeSteps.Add(new RecipeStep());
}
}
public class RecipeStep
{
public String Test1 { get; set; }
public String Test2 { get; set; }
public RecipeStep()
{
Test1= "Sample 1";
Test2 = "Sample 2";
}
}
}
As you can see from the code, ReadRecipeSteps should have 8 items and WriteRecipeSteps should have 6. However, whenever I run the program multiple times, the deserialized list gets added to,i.e., first run = ReadRecipeStesp.Count = 8, second run .Count = 16, etc.
Here is how I deserialize:
if (File.Exists(Path.Combine(path, fName)))
{
StreamReader objStreamReader = new StreamReader(Path.Combine(path, fName));
XmlSerializer x = new XmlSerializer(RecipeStepsWords.GetType());
RecipeStepsWords = (CRecipeStepsWords)x.Deserialize(objStreamReader);
objStreamReader.Close();
}
I'm using VS 2010 for this. What I would like is to always have the correct number of items in the lists, 8 and 6, instead of having them added to every time it gets deserialized. Any help with this would be appreciated.
Try adding an
[XmlArrayItem("ReadReceipe")] right after your xml array. Same for the WriteReciepe
So like this.
[XmlArray]
[XmlArrayItem("ReadReceipe")]
public List<RecipeStep> ReadRecipeSteps { get; set; }
I think what happens is you just keep adding those steps to the root, without having a parent node.
Let me know.
// Reset your object
RecipeStepsWords = new CRecipeStepsWords();
RecipeStepsWords = (CRecipeStepsWords)x.Deserialize(objStreamReader);
objStreamReader.Close();
I have a array:-
private string[][] barValues = new string[][] { new string[]{ "1.9", "5.8", "4.8", "Since Inception", "24-Jan 2014 to 24 Jun 2014" },
new string[]{"1.2", "16.5","9.8", "Year to date","01-Apr 2014 to 24-Jun 2014" },
new string[]{"11.6","28.8","23.5","Last quarter","01-Jan to 24-Jun 2014"} };
I want to convert this array into my custom list :-
List<Portfolio> list = new List<Portfolio>();
I tried doing :-
List<Portfolio> list=myArray.Cast<Portfolio>().ToList();
But I get a error:-
System.InvalidCastException: Cannot cast from source type to
destination type.
How do I do this conversion?
You will need to use the Select operator and assign your array of strings to your Portfolio object. Something like this:
myArray.Select(array => new Portfolio { Field1 = array[0], Field2 = array[1] }).ToList()
There is no "magic" conversion from string[] to your class PortFolio, you have to do it manually.
This could work:
List<Portfolio> portFolioList = barValues
.Select(sArr => new Portfolio
{
Values = sArr.Take(3).Select(double.Parse).ToList(),
Name = sArr.Skip(3).First(),
TimeSpan = sArr.Last()
}).ToList();
If you have a class like this:
public class Portfolio
{
public List<double> Values { get; set; }
public string Name { get; set; }
public string TimeSpan { get; set; }
}
Convert Array to string try this way
string[] arr = ...
List<object> list= new List<object>(arr);
foreach(object obj in arr)
list.add(obj);
var converted = barValues.Select(a => new {Value1 = a[0], Value2= a[1]}).ToArray();
This gives you an array of anonymous objects. Just replace my anonymous constructor with your constructor of Portfolio. Lambda variable a is the array that contains the string values.
Create a constructor taking the items from barValues and iterator over those.
like:
class PortFolio()
{
PortFolio(string e1, string e2, string e3, string period, string date)
{
// Copy to properties.
}
...
}
Then in the code for copying:
foreach (var barValue in barValues)
{
list.Add(new PortFolio(barValue[0], barValue[1], barValue[2], barValue[3], barValue[4]); }
}
C# is a strongly typed language. There is no out of the box way to magically cast a bunch of string into an object.
A correct way to do what you want is to first add a constructor on your Portfolio class that takes an array of strings and assign its values to your instance properties:
class Portfolio
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
public string Method { get; set; }
public string Period { get; set; }
public Portfolio(string[] values)
{
if (values != null)
{
this.Value1 = values.ElementAtOrDefault(0);
this.Value2 = values.ElementAtOrDefault(1);
this.Value3 = values.ElementAtOrDefault(2);
this.Method = values.ElementAtOrDefault(3);
this.Period = values.ElementAtOrDefault(4);
}
}
}
You can then use linq to build your list :
var portfoliosList = barValues.Select(values => new Portfolio(values)).ToList();
If required, you can do additional work in the constructor, for instance converting the values to decimal, the method to an Enum and the period as a couple of DateTime.
If you cannot modify the Portfolio class, because it's a third party component or out of your scope, you can create a factory class with a method containing that logic :
static class ProtfolioFactory
{
static public Portfolio BuildPortfolio(string[] values)
{
var portfolio = new Portfolio();
if (values != null)
{
portfolio.Value1 = values.ElementAtOrDefault(0);
portfolio.Value2 = values.ElementAtOrDefault(1);
portfolio.Value3 = values.ElementAtOrDefault(2);
portfolio.Method = values.ElementAtOrDefault(3);
portfolio.Period = values.ElementAtOrDefault(4);
}
return portfolio;
}
}
The linq query then becomes :
var list = barValues.Select(values => ProtfolioFactory.BuildPortfolio(values)).ToList();
I am trying to create a simple 'inventory' system that stores items with the key being an items name, and with the remaining information being stored as a value. However, I am having difficulty figuring out how to then read the information. For example, if I have say a list of 10 items, and I want to select the items 'type' information from the key 'television' outlined below, how could I do this?
television {large, 5, 3, false, dynamic, 0.8, 20}
Hashtable myItems = new Hashtable();
protected virtual bool OnAttempt_AddItem(object args) {
object[] arr = (object[])args;
string ItemType = (string)arr[0];
string ItemName = (string)arr[1];
int ItemAmount = (arr.Length == 2) ? (int)arr[2] : 1;
int ItemACanHave = (arr.Length == 3) ? (int)arr[3] : 1;
bool ItemClear = (bool)arr[4];
string ItemEffect = (string)arr[5];
float ItemModifier = (float)arr[6];
int ItemWeight = (int)arr[7];
// enforce ability to have atleast 1 item of each type
ItemACanHave = Mathf.Max(1, ItemACanHave);
myItems[ItemName] = new object[] {ItemType, ItemAmount, ItemACanHave, ItemClear, ItemEffect, ItemModifier, ItemWeight };
return true;
}
Create an item class to encapsulate the properties:
public class InventoryItem
{
public string Name;
public string Type;
public int Amount;
public int CanHave; // you should consider renaming this - it's very unclear what this could mean
public bool Clear;
public string Effect;
public float Modifier;
public int Weight;
}
Then you can use a Dictionary to store items:
Dictionary<string, InventoryItem> inventory = new Dictionary<string, InventoryItem>();
inventory["television"] = new InventoryItem
{
Name = "television", Type = "large", Amount = 5,
CanHave = 3, Clear = false, Effect = "dynamic",
Modifier = 0.8, Weight = 20
});
And you can look it up like this:
Console.WriteLine("Type of television is: ", inventory["television"].Type);
I would suggest you to consider the possibility of more than one item of a certain type in a inventory list, i.e. two or more television sets instead of only one.
Use a base class and derived classes:
public class InventoryItem
{
public string ItemType { get; set; }
public string ItemName { get; set; }
public int ItemAmount { get; set; }
public int ItemACanHave { get; set; }
public bool ItemClear { get; set; }
public string ItemEffect { get; set; }
public float ItemModifier { get; set; }
public int ItemWeight { get; set; }
}
public class Radio : InventoryItem
{
}
public class Television : InventoryItem
{
}
// TODO: add your derived classes
Use a List<InventoryItem> to store the collection:
List<InventoryItem> InventoryItems = new List<InventoryItem>();
Modify your method (don't forget to add exception handling, as sometimes you might get different input than the one you expected in the args object):
protected virtual bool OnAttempt_AddItem(object args)
{
// TODO: handle unboxing exceptions, size of the array etc
//
try
{
object[] arr = (object[])args;
switch (arr[0].ToString().ToLower())
{
// TODO: add other types (Radio etc)
case "television":
var tv = new Television();
tv.ItemType = (string)arr[0];
tv.ItemName = (string)arr[1];
tv.ItemAmount = (arr.Length == 2) ? (int)arr[2] : 1;
tv.ItemACanHave = (arr.Length == 3) ? (int)arr[3] : 1;
tv.ItemClear = (bool)arr[4];
tv.ItemEffect = (string)arr[5];
tv.ItemModifier = (float)arr[6];
tv.ItemWeight = (int)arr[7];
// enforce ability to have atleast 1 item of each type
tv.ItemACanHave = Math.Max(1, tv.ItemACanHave);
InventoryItems.Add(tv);
break;
default:
var genericItem = new InventoryItem();
genericItem.ItemType = (string)arr[0];
genericItem.ItemName = (string)arr[1];
genericItem.ItemAmount = (arr.Length == 2) ? (int)arr[2] : 1;
genericItem.ItemACanHave = (arr.Length == 3) ? (int)arr[3] : 1;
genericItem.ItemClear = (bool)arr[4];
genericItem.ItemEffect = (string)arr[5];
genericItem.ItemModifier = (float)arr[6];
genericItem.ItemWeight = (int)arr[7];
// enforce ability to have atleast 1 item of each type
genericItem.ItemACanHave = Math.Max(1, genericItem.ItemACanHave);
InventoryItems.Add(genericItem);
break;
//handle other cases
}
return true;
}
catch (Exception ex)
{
// log the error
return false;
}
}
Retrieve the filtered items like this:
var largeTvType = inventory.InventoryItems.OfType<Television>()
// filter by type (or other criteria)
.Where(tv => tv.ItemType == "large")
// select only the property your interested in (in the case below
// it will be always "television" because that's part of the
// logic inside the OnAttempt_AddItem method's switch statement)
.Select(tv => tv.ItemType);
Still, as ChrisWue suggested in his answer, if you know that your inventory lists will be very large, I'd recommend you to use a Dictionary<string, InventoryItem>, the string key being a unique inventory item identifier. It will be faster.