JSON Serializer returns empty file - c#

I have managed to create minimum reproducible example here:
internal class Program
{
static void Main(string[] args)
{
Program p = new Program();
Cache sc = new Cache();
sc.Enabled = true;
sc.Path = #"C:\File.txt";
p.WriteToJsonFile("Cache.json", sc);
}
private void WriteToJsonFile<T>(string filePath, T objectToWrite, bool append = false) where T : new()
{
TextWriter writer = null;
try
{
var contentsToWriteToFile = JsonSerializer.Serialize(objectToWrite);
writer = new StreamWriter(filePath, append);
writer.Write(contentsToWriteToFile);
}
finally
{
if (writer != null)
writer.Close();
}
}
internal class Cache
{
public string Path = string.Empty;
public bool Enabled;
}
}
File Cache.json is created, but it only contains {}, which means that these properties were ignored and not saved. Perhaps something is wrong with the WriteToJsonFile method, but in some cases it seems to work. And it was approved answer in one of stackoverflow questions.

JSON serializers in C# tend to make use of properties, not fields. These are just fields:
internal class Cache
{
public string Path = string.Empty;
public bool Enabled;
}
Make them properties:
internal class Cache
{
public string Path { get; set; } = string.Empty;
public bool Enabled { get; set; }
}

Related

Encrypting properties before saving class to xml file

I am attempting to save a few pieces of the connection string as encrypted text to an XML from my class.
Here is my class:
using System.Data.SqlClient;
using TechGuyComputing.CompleteOrganizerWPF.Data;
namespace TechGuyComputing.CompleteOrganizerWPF.MiscClasses
{
public class AppSetting
{
private string _dataSource;
private string _intitialCatalog;
private string _userId;
private string _password;
public string DataSource
{
set => _dataSource = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(_dataSource, GlobalConstants.EncryptionPassword);
}
public string IntitialCatalog
{
set => _intitialCatalog = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(_intitialCatalog, GlobalConstants.EncryptionPassword);
}
public string UserId
{
set => _userId = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(_userId, GlobalConstants.EncryptionPassword);
}
public string Password
{
set => _password = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(_password, GlobalConstants.EncryptionPassword);
}
public bool IntegratedSecurity { set; get; }
public bool MultipleActiveResultSets { set; get; }
public bool PersistSecurityInfo { set; get; }
}
internal static class AppSettings
{
public static AppSetting ApplicationSettings;
public static SqlConnection ConnectionString { get; private set; }
static AppSettings()
{
if (ApplicationSettings == null)
{
ApplicationSettings = XmlReader.GetAppSettingsFromXmlFile();
SetConnectionString();
}
}
public static void SaveAppSettings()
{
if (ApplicationSettings == null)
{
ApplicationSettings = new AppSetting();
}
XmlReader.WriteAppSettingsToXmlFile(ApplicationSettings);
SetConnectionString();
}
private static void SetConnectionString()
{
if (string.IsNullOrEmpty(ApplicationSettings.DataSource) || string.IsNullOrEmpty(ApplicationSettings.IntitialCatalog))
{
ConnectionString = new SqlConnection();
return;
}
var builder = new SqlConnectionStringBuilder
{
DataSource = ApplicationSettings.DataSource,
InitialCatalog = ApplicationSettings.IntitialCatalog,
IntegratedSecurity = ApplicationSettings.IntegratedSecurity,
MultipleActiveResultSets = ApplicationSettings.MultipleActiveResultSets,
PersistSecurityInfo = ApplicationSettings.PersistSecurityInfo,
UserID = ApplicationSettings.UserId,
Password = ApplicationSettings.Password
};
ConnectionString = new SqlConnection(builder.ConnectionString);
}
}
}
And this is how I am saving the XML file:
using System.IO;
using System.Xml.Serialization;
namespace TechGuyComputing.CompleteOrganizerWPF.MiscClasses
{
internal static class XmlReader
{
public static void WriteAppSettingsToXmlFile(AppSetting appSetting)
{
var xs = new XmlSerializer(typeof(AppSetting));
var tw = new StreamWriter(GlobalConstants.XmlFile);
xs.Serialize(tw, appSetting);
}
public static AppSetting GetAppSettingsFromXmlFile()
{
if (!File.Exists(GlobalConstants.XmlFile))
{
return new AppSetting();
}
using (var sr = new StreamReader(GlobalConstants.XmlFile))
{
XmlSerializer xs = new XmlSerializer(typeof(AppSetting));
return (AppSetting)xs.Deserialize(sr);
}
}
}
}
My save is working perfectly but it is not saving the values as encrypted strings.
I thought this would handle it on the fly but it's not doing anything:
public string DataSource
{
set => _dataSource = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(_dataSource, GlobalConstants.EncryptionPassword);
}
I am not getting any error messages, it's just not encrypting the data.
Any suggestions how I encrypt certain properties before they are saved?
EDIT:
I'd prefer not to encrypt the entire file if I can prevent it. I would like to only encrypt the properties that I choose.
Your problem is that XmlSerializer serializes only public properties and fields -- and the public properties in your AppSetting class are all unencrypted. From the docs
XML serialization serializes only the public fields and property values of an object into an XML stream. ...
XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.
Thus your options are:
Make public properties for the encrypted members and mark the plaintext properties with XmlIgnore like so:
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
[XmlElement("DataSource")] // Optionally change the element name to be <DataSource>
public string EncryptedDataSource { get; set; }
[XmlIgnore]
public string DataSource
{
set => EncryptedDataSource = Encryption.SimpleEncryptWithPassword(value, GlobalConstants.EncryptionPassword);
get => Encryption.SimpleDecryptWithPassword(EncryptedDataSource, GlobalConstants.EncryptionPassword);
}
Demo fiddle #1 here.
Switch to DataContractSerializer. First modify your class as follows:
[DataContract]
public class AppSetting
{
[DataMember(Name = "DataSource")]
private string _dataSource;
[DataMember(Name = "IntitialCatalog")]
private string _intitialCatalog;
[DataMember(Name = "UserId")]
private string _userId;
[DataMember(Name = "Password")]
private string _password;
// Remainder unchanged
Then modify XmlReader as follows:
public static void WriteAppSettingsToXmlFile(AppSetting appSetting)
{
var serializer = new DataContractSerializer(typeof(AppSetting));
using (var stream = new FileStream(GlobalConstants.XmlFile, FileMode.Create))
{
serializer.WriteObject(stream, appSetting);
}
}
public static AppSetting GetAppSettingsFromXmlFile()
{
if (!File.Exists(GlobalConstants.XmlFile))
{
return new AppSetting();
}
using (var stream = File.OpenRead(GlobalConstants.XmlFile))
{
var serializer = new DataContractSerializer(typeof(AppSetting));
return (AppSetting)serializer.ReadObject(stream);
}
}
The resulting properties will all be encrypted.
Demo fiddle #2 here.
Notes:
In WriteAppSettingsToXmlFile() you do not dispose the StreamWriter. This will leave the file open and possibly lead to errors later. Instead, do:
public static void WriteAppSettingsToXmlFile(AppSetting appSetting)
{
var xs = new XmlSerializer(typeof(AppSetting));
using (var tw = new StreamWriter(GlobalConstants.XmlFile))
{
xs.Serialize(tw, appSetting);
}
}
While properties serialized with XmlSerializer must be public, you can hide them a little by marking them with [Browsable(false)], [EditorBrowsable(EditorBrowsableState.Never)] and [DebuggerBrowsable(DebuggerBrowsableState.Never)],

How do I not allow a property to be null or empty when deserializing XML?

I know Json.net has an attribute [JsonRequired]; are there any XML methods that can do the same thing?.
I don't think there is, so I have made my own way of doing so, but I have stopped at how to handle List and T in reflection.
It's a great help if someone will tell me, thanks.
class Program
{
static void Main(string[] args)
{
string strin = "<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
TestXML ttt1 = XmlToModel<TestXML>(strin);
Console.ReadKey();
}
public static T XmlToModel<T>(string xml)
{
StringReader xmlReader = new StringReader(xml);
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
T t = (T)xmlSer.Deserialize(xmlReader);
Type typeT = typeof(T);
IfIsClass<T>(typeT, t);
return t;
}
private static void IfIsClass<T>(Type typeT, T t)
{
foreach (PropertyInfo p in typeT.GetProperties())
{
//here I don't konw how to handle List<T> and T
//if(is List<T> or T)
// IfisClass<T>(typeT,t);
IfIsNull<T>(p, t);
}
}
private static void IfIsNull<T>(PropertyInfo p, T t)
{
var at = p.GetCustomAttribute(typeof(NotNullAttribute));
var pvalue = p.GetValue(t);
if (at != null && string.IsNullOrEmpty(pvalue == null ? "" : pvalue.ToString()))
{
throw new Exception(string.Format("Field {0} not allow null or empty", p.Name));
}
}
}
public class TestXML
{
public string Fir { get; set; }
[NotNull]
public string Sec { get; set; }
[XmlElement("")]
public List<TestXML2> TestXML1 { get; set; }
}
public class TestXML2
{
public string Thir { get; set; }
public TestXML3 TestXML3 { get; set; }
}
public class TestXML3
{
[NotNull]
public string For { get; set; }
}
public class NotNullAttribute : Attribute
{
}
I don't 100% know what you're trying to accomplish with what you're doing right now, and I also don't know which values you don't want to be null, but I do see that there is a much easier way of deserialzing XML into objects (see here for some reference):
[XmlRoot("TestXML")]
public class TestXML
{
[XmlElement("Fir")]
public string Fir { get; set; }
[XmlArray("TestXML1")]
[XmlArrayItem("TestXML3", IsNullable = false)]
public TestXML3[] testxml3 { get; set; }
}
public class TestXML3
{
[XmlElement("For")]
public int For { get; set; }
}
public class Order
{
[XmlElement("number")]
public string Number { get; set; }
}
Once you have that, you can deserialize the xml wherever you want in the same file with:
string xml = #"<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
StringReader reader = new StringReader(xml);
XmlSerializer ser = new XmlSerializer(typeof(TestXML));
var data = (TestXML)ser.Deserialize(reader);
Null Checking
Using XML
Basically, you just add IsNullable = false inside the parenthesis of a [XmlArrayItem], for example, to make sure a specific value will not return null (It will skip over it). NOTE: I have only tested this on [XmlArrayItem], I do not know for sure if it will work on other Xml tags...
Using C#
If you really wanted to use C# and throw and exception if it's null (which sort of ruins the point of using [XmlElement] in the first place), you can do something like this instead (see here):
... //same code as before
var data = (TestXML)ser.Deserialize(reader);
//haven't tested this
foreach(obj dataobj in data){
if(dataobj == null) throw new NullReferenceException();
}
Using JSON
If you really want to use something like [JsonRequired], then why not just use it! You can convert the XML data to JSON data using Json.Net (see here):
string xml = #"<TestXML><Fir>f</Fir><TestXML1><TestXML3><For>444</For></TestXML3></TestXML1></TestXML>";
XmlDocument Test = new XMLDocument();
Test.loadXml(xml);
string json = JsonConvert.SerializeXmlNode(Test);
Now, you have your xml data in json format in string json, and you can do whatever you want to it, including setting up a map like I did with the xml and adding [JsonRequired]
someone told me,here is the answer.
public static T XmlToModel<T>(string xml)
{
StringReader xmlReader = new StringReader(xml);
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
T t = (T)xmlSer.Deserialize(xmlReader);
Type typeT = typeof(T);
IfIsClass(t);
return t;
}
private static void IfIsClass(object t)
{
var typeT = t.GetType();
foreach (PropertyInfo p in typeT.GetProperties())
{
if (typeof(System.Collections.ICollection).IsAssignableFrom(p.PropertyType))
{
var vValue = p.GetValue(t, null) as System.Collections.ICollection;
foreach(var item in vValue )
{
IfIsClass(item);
}
}
else
{
IfIsNull(p, p.GetValue(t, null));
}
}
}
private static void IfIsNull(PropertyInfo p, object pvalue)
{
var at = p.GetCustomAttribute(typeof(NotNullAttribute));
if (at != null && string.IsNullOrEmpty(pvalue == null ? "" : pvalue.ToString()))
{
throw new Exception(string.Format("field[{0}]not allow null or empty", p.Name));
}
}
Add a constructor
public TestXML()
{
Sec = "";
}

Derived Class Deserialization

I have a problem with deserialization with my logic simulation program.
Here are my element classes:
public class AndGateData : TwoInputGateData
{
}
public class TwoInputGateData : GateData
{
public TwoInputGateData()
{
Input2 = new InputData();
Input1 = new InputData();
}
public InputData Input1 { get; set; }
public InputData Input2 { get; set; }
}
public class GateData : ElementData
{
public GateData()
{
OutputData = new OutputData();
}
public OutputData OutputData { get; set; }
}
public class ElementData
{
public int Delay { get; set; }
public Guid Id { get; set; }
}
And here are classes responsible for sockets:
public class InputData : SocketData
{
}
public class SocketData
{
public Guid Id { get; set; }
public SignalData SignalData { get; set; }
}
SignalData is not important here. So, I won't write it (in order to keep this question clean) here unless somebody says it is necessary.
CircuitData is very important:
[XmlRoot("Circuit")]
public class CircuitData
{
[XmlElement(typeof(AndGateData))]
[XmlElement(typeof(OrGateData))]
public List<ElementData> elements = new List<ElementData>();
public List<WireData> wires = new List<WireData>();
public void AddElement(ElementData element)
{
elements.Add(element);
}
public void AddWire(WireData wire)
{
wires.Add(wire);
}
}
Wires are not important right now.
Now, I have written some Serialization:
public class CircuitDataWriter
{
public static void Write(object obj, string fileName)
{
var xmlFormat = new XmlSerializer(typeof(CircuitData));
using(Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None) )
{
xmlFormat.Serialize(fStream,obj);
}
Console.WriteLine("Circuit saved in XML format.");
}
}
It works just like I wanted, it produces that xml document:
<?xml version="1.0"?>
-<Circuit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-<AndGateData>
<Delay>10</Delay>
<Id>bfee6dd7-5946-4b7b-9d0b-15d5cf60e2bf</Id>
-<OutputData> <Id>00000000-0000-0000-0000-000000000000</Id> </OutputData>
-<Input1> <Id>7c767caf-79a9-4c94-9e39-5c38ec946d1a</Id> <SignalData xsi:type="SignalDataOn"/> </Input1>
-<Input2> <Id>d2cad8f8-8528-4db3-9534-9baadb6a2a14</Id> <SignalData xsi:type="SignalDataOff"/> </Input2>
</AndGateData>
<wires/>
</Circuit>
But I have problem with my DESERIALIZATION. Here is the code:
public static CircuitData Read()
{
var reader = new XmlSerializer(typeof(CircuitData));
StreamReader file = new StreamReader("Circuit.xml");
var returnCircuitData = new CircuitData();
returnCircuitData = (CircuitData) reader.Deserialize(file);
return returnCircuitData;
}
Now, it deserializes my Circuit.xml to object, but this object only contains Id and Delay, it does not contain Input1, Input2 or Output. So, it is treated like Element, not like AndGate. I tried to solve it out for a day but it seems that no one has that kind of problem.
I have a suggestion for you, make the Write method generic like this and create the serializer using objectToSerialize.GetType():
public static void Write<T>(T objectToSerialize, string fileName)
{
var xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
...
}
The XmlSerializer.Deserialize() method returns object, you can make your Read method generic like this:
public static T Read<T>(string fileName)
{
var serializer = new XmlSerializer(typeof(T));
using (StreamReader file = new StreamReader(fileName))
{
return (T)serializer.Deserialize(file);
}
}
Other than that you might want to read about:
XmlInclude that is used when you serialize derived classes.
XmlArray and XmlArrayItem that are used for controlling serialization of arrays

Object serializing/deserializing doesn't work

I have an extension method for System.Object to serialize and deserialize objects using Json.Net. this is my Extension methods:
public static void SaveToFile(this object data, string FileName)
{
using (StreamWriter writer = new StreamWriter(FileName))
{
string encode = WpfApplication.Helper.Encrypt(JsonConvert.SerializeObject(data));
writer.Write(encode);
writer.Close();
}
}
public static void LoadFromFile<t>(this object data, string FileName)
{
using (StreamReader reader = new StreamReader(FileName))
{
data = JsonConvert.DeserializeObject<t>(WpfApplication.Helper.Decrypt(reader.ReadToEnd()));
reader.Close();
}
}
and It's the class that I want to deserialize:
public class CardPack
{
#region Properties
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private List<FlashCard> cards;
public List<FlashCard> Cards
{
get { return cards; }
set { cards = value; }
}
private bool registered;
public bool Registered
{
get { return registered; }
set { registered = value; }
}
private int currentCardIndex;
public int CurrentCardIndex
{
get { return currentCardIndex; }
set { currentCardIndex = value; }
}
public string RegisterKey { get; set; }
public string ViewName { get; set; }
public List<FlashCard> TodayCards { get; set; }
#endregion
~CardPack()
{
foreach (FlashCard card in cards)
{
card.Check();
}
currentCardIndex = 0;
TodayCards = null;
this.SaveToFile(string.Format(System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Split(#"file:\\")[1] + #"\Packs\{0}.json", name));
}
but whenever I deserialize the class cards is empty and I don't know how to resolve the problem. Can anybody help me?
Update
I find the error when I had this code:
public CardPack(string Name)
{
this.name = Name;
this.LoadFromFile<CardPack>(string.Format(System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Split(#"file:\\")[1] + #"\Packs\{0}.json", name));
foreach (var item in cards)
{
if (item.NextTime == null)
{
int a = 0;
}
}
TodayCards = cards.Where(c => c.NextTime.Date == DateTime.Today).ToList();
}
because the application closed when it tries to run foreach (var item in cards)!
I asked here and found out that cards is empty!
update2 I serialized the CardPack object with a little different structure. in previous structure Cards property was read-only.
I found that data = JsonConvert.DeserializeObject<t>(WpfApplication.Helper.Decrypt(reader.ReadToEnd())); in extension method doesn't change to 'data' class then Cards in CardPack is always null. I'll ask a question to find out why I cant set the class from it's extension method later.

Data contracts: Ignore unknown types on deserialization

I have a plugin-based host application. Its settings are described as a data contract:
[DataContract(IsReference = true)]
public class HostSetup
{
[DataMember]
public ObservableCollection<Object> PluginSetups
{
get
{
return pluginSetups ?? (pluginSetups = new ObservableCollection<Object>());
}
}
private ObservableCollection<Object> pluginSetups;
}
Any plugin has its own settings type. E. g.:
[DataContract(IsReference = true)]
public class Plugin1Setup
{
[DataMember]
public String Name { get; set; }
}
and
[DataContract(IsReference = true)]
public class Plugin2Setup
{
[DataMember]
public Int32 Percent { get; set; }
[DataMember]
public Decimal Amount { get; set; }
}
At run-time, the user has configured host and plugins such a way:
var obj = new HostSetup();
obj.PluginSetups.Add(new Plugin1Setup { Name = "Foo" });
obj.PluginSetups.Add(new Plugin2Setup { Percent = 3, Amount = 120.50M });
Then, my application has saved its settings via DataContractSerializer. Plugin types were passed as known types to the serializer's constructor.
The question.
User physically removes assembly with "Plugin2" and then starts up my application.
So, when the host receives a list of available plugins, it knows nothing about serialized "Plugin2Setup" instance.
I want to ignore this instance, and let the user to work without "Plugin2" settings.
Is there any elegant way to do this?
I can store plugins' settings as data contracts serialized into strings:
public ObservableCollection<String> PluginSetups
but it's not handy and ugly.
Edit 1
The problem is how to deserialize HostSetup instance and ignore serialized Plugin2Setup instance.
Edit 2
My current solution is:
[DataContract(IsReference = true)]
public class PluginSetupContainer
{
[DataMember]
private String typeName;
[DataMember]
private String rawData;
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
if (SetupParameters != null)
{
using (var writer = new StringWriter())
using (var xmlWriter = new XmlTextWriter(writer))
{
var setupParametersType = SetupParameters.GetType();
var serializer = new DataContractSerializer(setupParametersType);
serializer.WriteObject(xmlWriter, SetupParameters);
xmlWriter.Flush();
typeName = setupParametersType.AssemblyQualifiedName;
rawData = writer.ToString();
}
}
}
[OnSerialized]
private void OnSerialized(StreamingContext context)
{
ClearInternalData();
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (!String.IsNullOrEmpty(typeName) && !String.IsNullOrEmpty(rawData))
{
var setupParametersType = Type.GetType(typeName, false);
if (setupParametersType != null)
{
using (var reader = new StringReader(rawData))
using (var xmlReader = new XmlTextReader(reader))
{
var serializer = new DataContractSerializer(setupParametersType);
SetupParameters = serializer.ReadObject(xmlReader);
}
}
ClearInternalData();
}
}
private void ClearInternalData()
{
typeName = null;
rawData = null;
}
public Object SetupParameters { get; set; }
}
[DataContract(IsReference = true)]
public class HostSetup
{
[DataMember]
public ObservableCollection<PluginSetupContainer> PluginSetups
{
get
{
return pluginSetups ?? (pluginSetups = new ObservableCollection<PluginSetupContainer>());
}
}
private ObservableCollection<PluginSetupContainer> pluginSetups;
}
May be it's terrible, but it works. :)
I think ideally you should have something on the lines of
[DataContract(IsReference = true)]
[MyPluginCustomAttribute]
public class Plugin1Setup
{
}
and when you application loads you should initialize obj.PluginSetups using reflection based on MyPluginCustomAttribute so only assemblies that are present have their types registered. So you won't have the problem of missing assemblies. You can also use Managed Extensibility Framework (MEF) instead of your own MyPluginCustomAttribute

Categories

Resources