1

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.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • For complex objects like `FontFamily` you can probably use a surrogate, see [Is it possible to use the .NET DataContractJSONSerializer to deserialize a JSON file formatted differently than it would usually serialize?](https://stackoverflow.com/q/41233345). If you were to provide a [mcve] we could probably show you how to do it. – dbc Feb 14 '20 at 01:13
  • But for primitives like `double` a surrogate won't work, see [DataContract surrogate for amplified value type](https://stackoverflow.com/a/8137943/3744182): *You cannot use surrogates for primitive types*. The suggestion there is to add some sort of surrogate property to the class. There's some very complex answer [here](https://stackoverflow.com/a/25368029) to [DataContractJsonSerializer and Enums](https://stackoverflow.com/q/794838) that might work for you, but I haven't tested it. Or do you only care about serialization and not deserialization? – dbc Feb 14 '20 at 01:20
  • Hi, I just added the test code. I'm also getting a new exception. I'm going to try building a surrogate tomorrow (It's to late right now). – Nicke Manarin Feb 14 '20 at 02:54
  • About the `Double`, I think that it's easier to add a check during cast. Perhaps. – Nicke Manarin Feb 14 '20 at 02:55
  • @dbc In the `Surrogate` class I was able to use `GetObjectToSerialize()` to convert each type needed into `string` but I don't have a method available that does the inverse. Only `GetDeserializedObject()`, but it returns the entire list of properties. If it returned each property, I would have the `TargetType` available. – Nicke Manarin Feb 14 '20 at 18:34

0 Answers0