This question already has an answer here:
Deserialize a List<AbstractClass> with newtonsoft.json
(1 answer)
Closed 7 years ago.
I have a Queue of an abstract class KVP. I queue 2 different objects which inherit from KVP. Everything works fine when I serialize the queue, but since KVP cannot be constructed it fails on deserialization.
If it was a single non generic object I could deserialize as dynamic, but I'm not sure how to deserialize a queue that could hold both events and IDs.
Sample code:
public virtual async Task<bool> LoadFromFile(string FileName, bool addToExistingQueue,bool DeleteFileAfterLoad = true)
{
try
{
IFile File = await PCLStorage.FileSystem.Current.LocalStorage.GetFileAsync(FileName);
var serializedText = await File.ReadAllTextAsync();
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText,jss);
if (!addToExistingQueue)
{
_queue = new ConcurrentQueue<T>();
}
while (mQueue.Count > 0)
{
_queue.Enqueue(mQueue.Dequeue());
}
if (DeleteFileAfterLoad)
{
await File.DeleteAsync();
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not load File. Exception Message: " + ex.Message);
return false;
}
}
public virtual async Task<bool> WriteToFile(string FileName)
{
try
{
Debug.WriteLine("Writing File: " + FileName);
var File = await FileSystem.Current.LocalStorage.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);
var serializedText = JsonConvert.SerializeObject(_queue.ToList(),jss);
await File.WriteAllTextAsync(serializedText);
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not write File with exception message: " + ex.Message);
return false;
}
}
You could
Enable TypeNameHandling (in both serialization and deserialization):
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var serializedText= JsonConvert.SerializeObject(mQueue, settings);
And then later
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText, settings);
This adds an extra "$type" property to your polymorphic classes, as is described here.
Before choosing this solution, for a discussion of possible security concerns using TypeNameHandling, see TypeNameHandling caution in Newtonsoft Json and How to configure Json.NET to create a vulnerable web API.
Write a custom converter that looks at the actual properties and chooses which derived class to use, as is discussed here: Deserializing polymorphic json classes without type information using json.net. This avoids the need for the extra "$type" property.
This should work if you use the following setting in the JSON.NET serialiser settings:
var settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto};
This will encode the object type into the JSON stream, which will help the deserializer determined the specific object type to construct during de-serialization.
Related
I have an app that reads and rights to a txt file in a json format. Everything is working fine except that from time to time the txt/json file for some reason becomes corrupted and the app crashes when trying to read it.
Here is the code...
User Class
public class User
{
public string UserName { get; set; }
}
usersFile.txt (json)
[{"UserName":"someUserName"}]
Reading Class
public static string myUsersFolder = #"c:\myUsersFilder";
string usersFile = Path.Combine(myUsersFolder, "usersFile.txt");
public void readUsersFromFile()
{
try
{
if (!File.Exists(usersFile))
throw new FileNotFoundException();// throws an exception if the file is not found
string jsonContent = File.ReadAllText(Path.Combine(myUsersFolder, usersFile));
List<User> users = JsonConvert.DeserializeObject<List<User>>(jsonContent);
foreach (var u in users)
{
User user = new User();
user.UserName = u.UserName;
UsersObservableCollection.Add(user);
}
}
catch (FileNotFoundException f)
{
Console.WriteLine("Couldn't read users from file: " + f.Message);
}
}
Error
'The invocation of the constructor on type 'MyProgramName.ViewModel.ViewModelLocator' that matches the specified binding constraints threw an exception.'
The issue is that if at some point the file usersFile.txt becomes corrupted, where the format is not right, for instance missing a } curly bracket, the app crashes.
[{"UserName":"someUserName"] // missing a curly bracket, app crashes
How can I prevent the app from crashing if the file is in the wrong format?
You should use the .NET JSON serializer (System.Text.Json). The .NET JSON library has a modern async API (How to serialize and deserialize (marshal and unmarshal) JSON in .NET.
The original exception you are experiencing is the result of the failed deserialization. You could (but shouldn't) wrap the deserialization part alone into a try-catch block. Also don't explicitly throw an exception (FileNotFoundException) just to catch it (even in the same context). Rather show the error message directly (or log it).
Exceptions are very expensive. You would always try to avoid them. Checking if the file exists successfully avoids the FileNotFoundException. Job done.
For this sake, the correct approach would be to validate the JSON before you try to deserialize it: avoid the exception instead of handling it.
The following example shows a fixed version of your code. It also incorporates JSON validation to avoid any malformed input related exceptions.
The example also uses the StreamReader to read from the file asynchronously. this helps to avoid freezing your application during file handling. Generally use async APIs where possible.
public async Task readUsersFromFileAsync()
{
if (!File.Exists(usersFile))
{
Console.WriteLine("Couldn't read users from file: " + f.Message);
}
using var fileReader = new StreamReader(Path.Combine(myUsersFolder, usersFile));
string jsonContent = await fileReader.ReadToEndAsync();
if (!await TryValidateJsonInputAsync(jsonContent, out IList<string> errorMessages))
{
foreach (string errorMessage in errorMessages)
{
Console.WriteLine(errorMessage);
}
return;
}
List<User> users = JsonConvert.DeserializeObject<List<User>>(jsonContent);
foreach (var u in users)
{
User user = new User();
user.UserName = u.UserName;
UsersObservableCollection.Add(user);
}
}
private Task<bool> TryValidateJsonInputAsync(string jsonContentToValidate, out IList<string> messages)
{
var jsonSchemaText = new StreamReader("json_schema.txt");
JSchema jsonSchema = JSchema.Parse(jsonSchemaText);
JObject jsonToValidate = JObject.Parse(jsonContentToValidate);
return jsonToValidate.IsValid(jsonSchema, out messages);
}
The example JSON schema used in the example (json_schema.txt file).
{
'description': 'A person',
'type': 'object',
'properties': {
'name': {'type': 'string'},
'hobbies': {
'type': 'array',
'items': {'type': 'string'}
}
}
}
See the Newtonsoft documentation Validating JSON to get more detailed information on how to validate JSON input.
But again, I recommend the JsonSerializer from the System.Text.Json namespace as it offers an async API.
I get a file with different content (currently 4 different classes), either
<ClassA><!-- content --></ClassA>
or
<ClassB><!-- content --></ClassB>
or ...
At time of parsing I have no further information which class is in the file.
So, currently, I try to parse by trial and error:
try
{
ClassA result = (ClassA)new XmlSerializer(typeof(ClassA)).Deserialize(reader);
if(!(result is null)) { \\do something }
}
catch (Exception) {}
And the same for ClassB and so on ...
Is there a more elegant way to parse the classes?
I can give all classes the same base class, although the are quite different in their form.
I solved this issue with XmlSerializer.CanDeserialize(XmlReader)
using (var reader = XmlReader.Create(stream))
{
foreach (var type in types)
{
var serializer = new XmlSerializer(type);
if (serializer.CanDeserialize(reader))
{
return serializer.Deserialize(reader);
}
}
throw new XmlException("Invalid xml type");
}
I have a dictionary of abilityobjects <id, abilityObj> that I'm trying to serialize in XML. Because you can't XML Serialize a dictionary, I change it into a list on serialization
public class BattleSerializable : TyrantSerializable
{
[XmlIgnoreAttribute]
[NonSerialized]
[DoNotSerialize]
public Dictionary<int, AbilityObjectSerializable> battleObjects;
public List<AbilityObjectSerializable> serializedBattleObjects
{
get
{
if (battleObjects == null)
{
battleObjects = new Dictionary<int, AbilityObjectSerializable>();
}
return battleObjects.Select(x => x.Value).ToList();
}
set
{
battleObjects = value.ToDictionary(x => x.entityId, x => x);
}
}
It serializes correctly. I.e. the XML that gets saved makes sense
<BattleSerializable>
...
<serializedBattleObjects>
<AbilityObjectSerializable xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" d3p1:type="FireballObject">
<hexDirection>southeast</hexDirection>
<gridX>0</gridX>
<gridZ>7</gridZ>
<entityId>3</entityId>
<lastAnimation>STATUE</lastAnimation>
<timer>0</timer>
<abilityPos>2</abilityPos>
<abilityType>FIREBALL</abilityType>
<health>100</health>
<tilesPerTurn>2</tilesPerTurn>
<jump>1</jump>
<fall>99</fall>
<damage>5</damage>
<lineTraversed>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</lineTraversed>
<moveDirection>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</moveDirection>
</AbilityObjectSerializable>
</serializedBattleObjects>
</BattleSerializable>
But when I try to then -load- this XML and turn it into actual C# objects, this list comes in empty for some reason, causing the app to blow up.
What am I missing? All the other lists in this class serialize/deserialize correctly.
My load code:
public BattleSerializable Load(string path)
{
var serializer = new XmlSerializer(typeof(BattleSerializable));
try
{
using (var stream = new FileStream(path, FileMode.Open))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
string xmlString = xmlDoc.InnerXml;
BattleSerializable bs = (BattleSerializable)this.LoadFromXML(xmlString);
return bs;
}
}
catch (Exception e)
{
throw new SettingLoadException("Settings failed validation");
}
}
The way a lot of serializers work is by calling Add on a list, only actually assigning anything back to the setter if the serializer created the list (perhaps because it was null, or fixed size such as an array). So imagine the serializer doing:
var list = obj.SomeProperty;
while (moreOfTheSame)
list.Add(ReadOneOfThose());
It never calls the setter, so any logic in there: irrelevant. You'll probably need a custom list type here, or perhaps more simply: have a nice simple POCO/DTO model that just maps to the serialization shape with no fun logic, and project between this model and your domain model separately to serialization.
So I have downloaded an asset for Unity called "JSON .NET For Unity", and I got it working, but I have a problem.
I have different classes and variables and I want to store them, the problem its that I dont know how to save them in the same file. I have a different method for each thing that I want to save but I dont know how to do that in the same method or making it write in the same file.
This is one example: In this method I save the class named World and I get it from the file. I have other methods like this one that asks for different things (a list, a variable...)
public void SaveWorld(World worldToSave)
{
SaveSystem.Init();
string json = JsonConvert.SerializeObject(worldToSave, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
File.WriteAllText(SaveSystem.SAVE_FOLDER + "/Save.json", json);
}
public World LoadWorld()
{
World saveWorld = null;
if (File.Exists(SaveSystem.SAVE_FOLDER + "/Save.json"))
{
string saveString = File.ReadAllText(SaveSystem.SAVE_FOLDER + "/Save.json");
saveWorld = JsonConvert.DeserializeObject<World>(saveString, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});
}
return saveWorld;
}
Then for saving I would call this:
void Save()
{
saveLoadSystem.SaveWorld(worldHandler.World);
saveLoadSystem.SaveInstalledObjects(worldHandler.installedObjectList);
saveLoadSystem.SaveUnits(unitHandler.unitList);
}
Where saveLoadSystem is the script that has all the methods.
Thank you
Edit: Using Hacettepe Hesabı answer.
I can save correctly like he said and saving a class that only contains an int called numData and another script with a List of ints I get this:
{
"$id": "1",
"numData": 2
}[
0,
1
]
The problem is that when I load I get this error:
JsonReaderException: Additional text encountered after finished reading JSON content: [. Path '', line 4, position 1.
For loading Im using the next method:
public DATA LoadData()
{
DATA saveData = null;
if (File.Exists(SaveSystem.SAVE_FOLDER + "/Save.json"))
{
string saveString = File.ReadAllText(SaveSystem.SAVE_FOLDER + "/Save.json");
saveData = JsonConvert.DeserializeObject<DATA>(saveString, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});
}
return saveData;
}
To save them all to the same file you could have the three instances in the same class and then save that class.
I built my *.dbml file with the required tables and that generated the relationships, 1 to many.
One of my methods in my WCF Service library has this query
IQueryable<Client>localClient = from c in db.Clients
where c.ClientID.Equals(2612)
select c;
foreach(Client currentClient in localClient)
{
//Call serialize method here
}
One of the table which client has a 1 to many relationships is Client - Employee
With 'localClient', I want to serialize that and return to the invoker. However it tells me that the the XML document could not be formed.
This is the InnerException:
A circular reference was detected while serializing an object of type TestDB_Public.Employee.
My serialization code
public string Serialize(object o, XmlSerializerNamespaces ns)
{
try
{
System.IO.MemoryStream m = new System.IO.MemoryStream();
if (ns != null)
serializer.Serialize(m, o, ns);
else
serializer.Serialize(m, o);
m.Position = 0;
byte[] b = new byte[m.Length];
m.Read(b, 0, b.Length);
return System.Text.UTF8Encoding.UTF8.GetString(b);
}
catch (Exception ex)
{
return "Ex = " + ex.ToString();
}
}
Is serialization of IQueryable<> with 1 to many relationships not possible?
What exactly is the error message? Note that you have to serialize something concrete like a list or array of objects (not a query).
If you want queryable over the wire, look at ADO.NET Data Services, which does this.
Also - have you set the serialization mode to "unidirectional" in the dbml designer? As long as there are no loops, it should work fine.
You can't serialise an object graph that has cyclical relationships:
class Employee
{
Employee Manager;
List<Employee> Employees;
}
var bossMan = new Employee();
var emp2 = new Employee{Manager = bossMan}
var bossMan.Employees.Add(emp2);
If you now try to serialise bossman or emp2, you will get the exception.
Have a look at this post, check the Cyclic Object Graphs for a solution.
Marc:
For some reason it is not allowing me to add a comment;
I added this
[Table(Name="dbo.Client")]
[DataContract(IsReference=true)]
public partial class Client: INotifyPropertyChanging, INotifyPropertyChanged
{
..//
private EntitySet<ClEmp> _ClEmp;
[Association(N...)]
[DataMember(Order=70, EmitDefaultValue=false)]
public EntitySet<ClEmp> ClEmps
}
My serialization is this:
DataContractSerializer ser =
new DataContractSerializer(typeof(Client));
var ms = new System.IO.MemoryStream();
ser.WriteObject(ms, r);
ms.Seek(0, System.IO.SeekOrigin.Begin);
var sr = new System.IO.StreamReader(ms);
var xml = sr.ReadToEnd();
when i look at var xml, i do not get my enity set ClEmp.