I'm trying to serialize and save as JSON the settings of my app and I'm having some issues with the serialization of some types.
The settings are loaded when the app starts into a ResourceDictionary
which gets added into the Application.Current.Resources.MergedDictionaries
since it's a WPF
application.
When the app is closing, I must get that ResourceDictionary
, serialize to JSON and write to disk.
Currently I'm facing three problems:
1) Double
properties which have no decimal value are being serialized and deserialized as Int32
, which causes some problems when app tries to cast Object
to Double
. Isn't there anyway to force the serialization as double?
public double StartupTop
{
get => (double)GetValue();
set => SetValue(value);
}
2) FontFamily
properties are strange. Instead of exporting just the font name, the whole font family is getting exported (light, italic, etc).
3) I'm also getting an exception when trying to deserialize the json:
Type 'System.Windows.Markup.XmlLanguage' cannot be serialized.
public enum AppTheme
{
Light,
Medium,
Dark,
VeryDark,
}
Working case:
var json = "";
var dic = new ResourceDictionary
{
{"EnumProperty", AppTheme.Dark},
{"IntProperty", 42},
{"DoubleProperty", 666D},
{"FontFamilyProperty", new FontFamily("Segoe UI")},
{"ColorProperty", Colors.Blue},
{"RectProperty", new Rect(10, 20, 30, 40)},
{"ArrayListProperty", new ArrayList
{
1, 2, 3, 4, 5
}},
};
//Save settings.
using (var ms = new MemoryStream())
{
var settings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true,
EmitTypeInformation = EmitTypeInformation.Always,
DataContractSurrogate = new SettingsSurrogate(), //Use the Surrogate only during serialization.
KnownTypes = new List<Type>
{
typeof(AppTheme),
typeof(ArrayList),
typeof(Color),
typeof(FontFamily),
typeof(Rect),
}
};
var ser = new DataContractJsonSerializer(typeof(Dictionary<string, object>), settings);
ser.WriteObject(ms, dic.Keys.Cast<string>().ToDictionary(x => x, x => dic[x]));
json = Encoding.UTF8.GetString(ms.ToArray());
//File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Test.json"), json);
}
//Load settings.
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
var settings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true,
EmitTypeInformation = EmitTypeInformation.Always,
KnownTypes = new List<Type>
{
typeof(AppTheme),
typeof(ArrayList),
typeof(Color),
typeof(FontFamily),
typeof(Rect),
}
};
var ser = new DataContractJsonSerializer(typeof(Dictionary<string, object>), settings);
var list = ser.ReadObject(ms) as Dictionary<string, object>;
//Ignore empty settings.
if (list == null || list.Count == 0)
return;
foreach (var item in list)
{
if (dic[item.Key] != item.Value || dic[item.Key].GetType() != item.Value.GetType())
throw new Exception("Wrong value");
}
}
Surrogate
internal class SettingsSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj.GetType() == typeof(FontFamily))
return ((FontFamily) obj).Source;
if (obj is Color color)
return $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
if (obj is Rect rect)
return $"#{rect.Left};{rect.Top};{rect.Width};{rect.Height}";
//Convert value to string, using whatever format I need.
//Right now I'm following the XAML set of formats for the types.
return obj;
}
//Other members go here, but they are not necessary for me.
}
I'm using both types multiple times in different properties. Is there anyway to control the serialization of the types?
EDIT:
I'm now using a surrogate to convert some values to string before serialization. But this leaves me with the task to manually convert back each property on deserialization, since there's no Type
available for me.