I'm writing a game saving function. When serializing the game world, areas has loop reference:
public class Area
{
List<Area> pathArea;
List<int> pathCost;
}
Before area1 is serialized and get an $id, the pathArea gets more area2, area3, that also have area1 as a neighbor.
How to work around this?
I use a Relink function to solve parent back-reference:
public class District
{
private Area parent; //pseudo// parent_Area::Relink { all children => parent = self }
}
Value pair to solve dictionaries:
public List<KeyValuePair<District, Building>> DistrictBasePair
{
get => districtBase.ToList();
set { districtBase = value.ToDictionary(x => x.Key, x => x.Value); }
}
But neighborhood loops... I wonder how to serialize them? Do I have to write a remapping function?
1: Remapping post process from name to obj
foreach(var n in pathAreaName)
{
pathArea.Add(DictName2Area[n];
}
2: An indepedent Path Manager class design. I prefer not to use this in a small game.
What is the common way to solve this problem? Does Newtonsoft or .Net Json provide a function?
Like a ref id option can save you a lot of time writing you own ID mapping functions.
Question 2... The game world are serialized depth first way, to an ugly biased tree.
How can I change some setting or config, to get a breadth first balance tree?
When I simply serialize the world object, it turns into an biased tree, because of the complex class relations above.
If I serialize the world layer by layer, area, district, sector... I can't use the automatic object ref-id system in json.
How do you get a balanced tree? Can I control the json serialization, and make ref to an object that not yet appears?
area1:
"pathArea":{"$ref":"5","$ref":"6"}
...
area2:"$id":"5"
...
area3:"$id":"6"
Or can I get these ID, and use them when I serialize the finer layer.
Areas.json
area:"$id":"123"
Districts.json
district:"parent":{"$ref":"123"}
Edit: I migrated the code from Newtonsoft.Json to System.Text.Json (.Net 6.0). I found that the later can handle this, in area1's neighbor, area2 show area1 as a $ref.
In System.Text.Json, there's no problem 1.
Still, the tree is biased. I want a World -> {area1...} -> {district1_1...} structure. What I got is a tree that put like everything into area1, and this tree is hard to read, may have some problem when I write more info.
I found that:
M1. A [JsonPropertyOrder(10)] decorator can delay some fields being serialized within the object.
M2. A speciel designed helper/manager class can delay the info serialization after the object.
I've already written lots of medium classes in XML serialization, to make moderable prototypes. But the M2. helper classes actually change the model logic.
Should I change the game logic, just to make a prettier object tree?
Or small hacks like M1, and many more, are sufficient to write a good saving function?
When I deserialize an XML document with XmlTextReader, a textual element for which there is no corresponding class is simply ignored.
Note: this is not about elements missing from the XML, which one requires to be present, but rather being present in the XML text, while not having an equivalent property in code.
I would have expected to get an exception because if the respective element is missing from the runtime data and I serialize it later, the resulting XML document will be different from the original one. So it's not safe to ignore it (in my real-world case I have just forgotten to define one of the 99+ classes the given document contains, and I didn't notice at first).
So is this normal and if yes, why? Can I somehow request that I want to get exceptions if elements cannot be serialized?
In the following example-XML I have purposely misspelled "MyComandElement" to illustrate the core problem:
<MyRootElement>
<MyComandElement/>
</MyRootElement>
MyRootElement.cs:
public class CommandElement {};
public class MyRootElement
{
public CommandElement MyCommandElement {get; set;}
}
Deserialization:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyRootElement));
XmlTextReader xmlReader = new XmlTextReader(#"pgtest.xml");
MyRootElement mbs2 = (MyRootElement)xmlSerializer.Deserialize(xmlReader);
xmlReader.Close();
As I have found out by accident during further research, this problem is actually ridiculously easy to solve because...
...XmlSerializer supports events! All one has to do is to define an event handler for missing elements
void Serializer_UnknownElement(object sender, XmlElementEventArgs e)
{
throw new Exception("Unknown element "+e.Element.Name+" found in "
+e.ObjectBeingDeserialized.ToString()+" in line "
+e.LineNumber+" at position "+e.LinePosition);
}
and register the event with XmlSerializer:
xmlSerializer.UnknownElement += Serializer_UnknownElement;
The topic is treated at MSDN, where one also learns that
By default, after calling the Deserialize method, the XmlSerializer ignores XML attributes of unknown types.
Not surprisingly, there are also events for missing attributes, nodes and objects.
So is this normal and if yes, why?
Because maybe you're consuming someone else's XML document and whilst they define 300 different elements within their XML, you only care about two. Should you be forced to create classes for all of their elements and deserialize all of them just to be able to access the two you care about?
Or perhaps you're working with a system that is going to be in flux over time. You're writing code that consumes today's XML and if new elements/attributes are introduced later, they shouldn't stop your tested and deployed code from being able to continue to consume those parts of the XML that they do understand (Insert caveat here that, hopefully, if you're in such a situation, you/the XML author don't introduce elements later which it is critical to understand to cope with the document correctly).
These are two sides of the same coin of why it can be desirable for the system not to blow up if it encounters unexpected parts within the XML document it's being asked to deserialize.
I am using json.net library to serialize an object which has a decimal value that represents a cost. When serializing, I want the json to look something like '$400,000' instead of '400000.0'.
Is there a way that I can accomplish this in an easy and efficient way? This object contains many other secondary objects and subsequent properties.
The application will be used locally and the specs say that the output should be in human readable format. Culture variance isn't of any importance in this context.
There's no easy way to perform this since you can't work around a fact that serializer is directly accessing your properties.
If you need a formatted JSON output I would recommend writing a helper class that wraps the class you want to expose. Unfortunately I've done this once for some XML report and really the easiest way went something like this:
class PersonXml
{
Person _person;
void PersonXml(Person person) { _person = person; }
string Age { get { return _person.Age + " years"; } }
}
This is just a simple example I wrote on the fly but the principle is always the same. Even if some advanced JSON serializer offered me ways to format my output, I'd really keep this concept out of my main objects in a separate file with separate helper classes.
Again this isn't quite the solution, in my opinion it should never come to this but if it does, this is the lesser of the evils in my experience.
Also, just note that if you reference the class in your property getters there's a danger of null reference.
As D Stanley suggested, I changed the field type to string from decimal as data in it was only being read and not used for data manipulation.
What I am trying to do is load in objects from an XML save file. The problem is those objects are configurable by the user at runtime, meaning i had to use reflection to get the names and attributes of those objects stored in an XML file.
I am in the middle of a recursive loop through the XML and up to the part where I need to create an object then thought ..... ah - no idea how to do that :(
I have an array stuffed with empty objects (m_MenuDataTypes), one of each possible type. My recursive loading function looks like this
private void LoadMenuData(XmlNode menuDataNode)
{
foreach (object menuDataObject in m_MenuDataTypes)
{
Type menuDataObjectType = menuDataObject.GetType();
if (menuDataObjectType.Name == menuDataNode.Name)
{
//create object
}
}
}
I need to put some code where my comment is but I can't have a big switch statement or anything. The objects in my array can change depending on how the user has configured the app.
You want to use Activator.CreateInstance(Type)
object instance = Activator.CreateInstance(menuDataObjectType);
for this to work efficiently, you may need to restrict the dynamically created instances to implement an interface
ICommonInterface i = (ICommonInterface)Activator.CreateInstance(menuDataObjectType)
That way, the dynamically created object becomes usable - you can call interface methods on it.
If you're instantiating a graph of objects, would the XmlSerializer or DataContractSerializer be more appropriate?
Activator.CreateInstance
I'm facing a problem that I don't know how to solve and am hoping the community can help.
I'm writing an app that manages "Lead" objects. (These are sales leads.) One part of my program will import leads from a text file. Now, the text file contains lots of potential leads, some of which I will want to import and some of which I won't.
For ease of programming (and use), I'm parsing the text file into a List<Lead> object, and using a DataGridView to display the leads by setting the DataSource property of the DataGridView.
What I want to do is add a column to the grid, called "Import," with a checkbox that the user can check to indicate whether or not each lead should be imported.
My first thought is to derive a class from Lead:
public Class LeadWithImportCheckbox : Lead
{
bool bImport = false;
public bool Import
{
get { return bImport;}
set { bImport = value;}
}
}
However, the parsing engine returns a list of Lead objects. I can't downcast a Lead to a LeadWithImportCheckbox. This fails:
LeadWithImportCheckbox newLead = (LeadWithImportCheckbox)LeadFromParsingEngine;
This is an invalid cast.
The other option I see is to create a constructor for LeadWithImportCheckbox:
public LeadWithImportCheckbox(Lead newlead)
{
base.Property1 = newlead.Property1;
base.Property2 = newlead.Property2;
....
base.Property_n = newlead.Property_n;
}
This is problematic for two reasons. One, the Lead object has several dozen properties and writing this constructor is a PITA.
But worse, if I ever change the underlying structure of Lead, I need to remember to go back and change this constructor for LeadWithImportCheckbox. This is a danger to my code maintenance.
Is there a better way of accomplishing my goal?
or, to avoid the PITA aspect, use reflection... (try this...)
EDIT: use property, not Field as I had originally written...
public class NewLead : Lead
{
public bool Insert;
public NewLead(Lead lead, bool insert)
{
Insert = insert;
foreach (PropertyInfo pi in typeof(Lead).GetProperties())
GetType().GetProperty(pi.Name).SetValue
(this, pi.GetValue(lead,null), null);
}
}
public class LeadListItem
{
public Lead Lead { get; set; }
public bool ShouldImport { get; set; }
}
i.e. don't copy the Lead object's contents, just store a reference to it in a new LeadListItem object, which adds extra info "outside" the original object.
If you want the properties of Lead to appear in the grid, there is almost certainly a way of doing that. Why not ask that question, instead of downvoting me for telling you the right answer to this question!
A couple options you might have missed:
You could update the Lead object itself to have an Import property (that defaults to false).
You could have your "ImportLead" object treat the Lead as payload (even make it generic, if you want), so you don't need the big constructor.
Build a new Lead object list or enumerable that only contains the objects you want to import in the first place.
You can only downcast, if the object to be downcast is really an object of that type.
An easier way to solve your problem would be to have a DisplayLead class, such as:
public class DisplayLead {
Lead lead;
bool bImport;
}
which would also help you separating stored data from their representation in a GUI.
What you want to do is display the checkbox column on your grid and not have it related at all to your Lead objects. You use the marked columns (and possible the original List) to build a new set of List which will be your import list.
Then handle whatever you wish to do with the newly created List.
Edit: One thing to be careful of when working with lists is the fact every class object is actually only a pointer to the class so if you work with the original list and do something like:
List<Lead> Importable = new List<Lead>();
for(int i=0, i++, i<viewGrid.Count)
if(viewGrid[i].CheckedColumn.Checked)
Importable.Add(OriginalList[i]);
That objects will exist in both lists and if you edit data of a Lead on either list both will be changed.
I cannot downcast to something it is not. If the object was instantiated as a Lead, then it can't be downcast to any derived class. If it were instantiated as a LeadWithImportCheckbox and then returned to your code as Lead, then you can downcast it.
Protip: Check type at runtime with is operator.
There are many ways to do this, but the "right" way pops out because of what you said, here:
For ease of programming (and use), I'm
parsing the text file into a
List object, and using a
DataGridView to display the leads by
setting the DataSource property of the
DataGridView.
What I want to do is add a column to
the grid, called "Import," with a
checkbox that the user can check to
indicate whether or not each lead
should be imported.
Your Lead object stands well on its own, and you want to attach some metadata to it -- you don't want to create another Lead classification (i.e. the LeadWithImportCheckbox class).
So, the best approach in your case is to have a class like so:
public class LeadInfo
{
private Lead lead;
private bool shouldImport;
public LeadInfo(Lead lead)
{
this.lead = lead;
this.ShouldImport = false;
}
public bool ShouldImport
{
get { return shouldImport; }
set { shouldImport = value; }
}
}
This will scale well when you want to add more metadata to your list, like if you want to send yourself email reminders about them every week.
I've seen the correct solution listed so many times I feel like a heel posting it again, but the best way to approach this is to write a wrapper for the Lead object that includes the import flag.
If the properties of the Lead object don't appear in the GridView because you're databinding to the object, then write passthrough properties that mirror the Lead properties on the wrapper object.
The issue is that you want something displayed to the user that isn't an inherent part of the data model. The answer is to wrap the data before presenting it to the user so you can control what they see without changing the underlying model.
If you're concerned that the Lead object will change so many times in the future that changes to the wrapper will be cumbersome, you could look into dynamic code generation based on the Lead object that will automatically generate a wrapper object with the same fields as the Lead object plus the import flag. Though frankly, that's a lot more work than you'll probably need for something as straightforward as this.
As a quick and dirty solution, you can create your 'checkbox' object as a different object that contains an instance of Lead.
public GridLead {
public bool Import { get; set; }
public Lead Lead { get; set; }
}
This way you can easily add more 'grid' properties to this object, while still always retaining a reference to the Lead details without hardcoding property cloning into it.
Recommend you try modifying (upgrading) your imported lead objects.
Try starting with the examples here...
If your Lead class had a copy constructor (e.g. "Lead(Lead otherLead)"), LeadWithImportCheckbox would inherit that and you could just call the base Lead constructor in the LeadWithImportCheckbox constructor - hence no need for LeadWithImportCheckbox to be aware of the details of Lead.