Search code examples
c#jsonserializationjson.netdeserialization

How to de[serialize] only selected private fields of a type from and to JSON using Json.NET?


I have a type and I need to round-trip only selected private fields of that type to and from JSON, ignoring all other fields and properties whether public or private. How can I do that using Json.NET?

My current approach is to use an intermediate Dictionary<string, object>. Serialization works, but deserialization does not

  • For serialization, I added a method to my type that constructs a Dictionary<string, object> and populates it with the required fields by name. Later, I serialize the dictionary.

    This works.

  • For deserialization, I deserialize to a Dictionary<string, object> and then attempt to populate the fields of my type by name and value using reflection.

    This does not work because the object value in the dictionary has the wrong type.

How can I fix this?

Sample class:

public partial class MyClass
{
    // Parameters is a instance of class which is created for storing some values.
    private Dictionary<string, Parameters> SomeParameters;
    private NotificationList notifications ;
    private string someKey;
    private int someNumber;
    private char aChar;

    public MyClass() { }
    public MyClass(Dictionary<string, Parameters> someParameters, NotificationList notifications, string someKey, int someNumber, char aChar) =>
        (this.SomeParameters, this.notifications, this.someKey, this.someNumber, this.aChar) = (someParameters, notifications, someKey, someNumber, aChar);

    //...
    //other properties and fields which should not be [de]serialized for this specific operation.
    public string IgnoreMe { get; set; } = "ignore me";
    private string alsoIgnoreMe = "ignore me";
}

public record Parameters(string Key, string Value);
public class NotificationList : List<string> { }

Sample class methods used to create my dictionary, and recreate MyClass from a dictionary:

public partial class MyClass
{
    public Dictionary<string, object> ToDictionary() => new()
    {
        { nameof(SomeParameters), SomeParameters},
        { nameof(notifications), notifications },
        { nameof(someKey), someKey },
        { nameof(someNumber), someNumber },
        { nameof(aChar), aChar },
    };
    
    public static MyClass FromDictionary(Dictionary<string, object> settings)
    {
        var instance = new MyClass();

        foreach (string settingValue in settings.Keys)
        {
            Type type = typeof(Dictionary<string, object>);
            FieldInfo? fieldInfo = type.GetField(settingValue, BindingFlags.NonPublic | BindingFlags.Instance);
            // This does not work -- fieldInfo is null, and settings[settingValue] is not the correct type.
            fieldInfo?.SetValue(instance, settings[settingValue]);
        }           
        
        return instance;
    }
}

Sample JSON:

{
  "SomeParameters": {
    "name": {
      "Key": "hello",
      "Value": "There"
    }
  },
  "notifications": [],
  "someKey": "some key",
  "someNumber": 101,
  "aChar": "z"
}

So key(string) is my field name and object is the field value itself. There is no problem with serializing but I cannot deserialize back the object(value) to original type. I searched a lot and could not find any simple solution.

NOTE: I cannot put these variables inside a class and deserialize back. Especially i need to do this operation inside that class and set the values back. It must be done for every field separately.

Demo fiddle here.


Solution

  • First of all thanks to dbc for providing great help. I've updated my need from his answer.

    Firstly, i shortly asked as "how can i deserialize and set back to instance of the MyClass" but setting back to instance didnt work. So i changed the way little bit. Now i dont deserialize to instance but i deserialize bak to class instance itself by this.

    This is the class which i need to [de]serialize. SaveSettings sends the parameters to it's overload method. Overload method takes parameters and add the field names, field type(field type is converted it's string representation) and finaly field's value into settings Dictionary which holds all field variables.

    Only thing is to do after adding all fields/properties which is asked for is to serialize and save to disk.

    When it comes to deserialization, name of field, it's type and field value(content) is set back inside the class like this:

    fieldInfo?.SetValue(this, ConvertToType(settings[settingValue].Value, typeOfObject))

    I've taken ConvertToType method from dbc

    And I'm determining type of field by

    Type typeOfObject = Type.GetType(settings[settingValue].Key);

    This can retrieve the type back which is stored in disk as string. (Breafly, string to type, type to string)

        public class MyClass
        {
            private Dictionary<string, KeyValuePair<string, object>> settings = new Dictionary<string, KeyValuePair<string, object>>();
            
            private NotificationList notifications ;
                    
            private string key;
                    
            private string someStrVal;
    
            private int someNumber = 0;
    
            public void SaveSettings()
            {
                SaveSettings(new Dictionary<string, KeyValuePair<string, object>>()
                {
                    { nameof(parameters), new KeyValuePair<string, object>(
                       typeof(Dictionary<string, Parameters>).ToString() ,parameters) },
    
                    { nameof(notifications), new KeyValuePair<string, object>(
                        typeof(NotificationList).ToString(), notifications) },
    
                    { nameof(key), new KeyValuePair<string, object>(
                        typeof(string).ToString(), key) },
    
                    { nameof(someStrVal), new KeyValuePair<string, object>(
                        typeof(string).ToString(), someStrVal) },
    
                    { nameof(someNumber), new KeyValuePair<string, object>(
                        typeof(int).ToString(), someNumber) }
    
                });
            }
    
            public void SaveSettings(Dictionary<string, KeyValuePair<string, object>> args)
            {
                if (_IsSettingsVerified == SheetVerification.Verified)
                {
                    foreach (string name in args.Keys)
                        settings.Add(name, args[name]);
    
                    string ayarlar = JsonConvert.SerializeObject(settings, Formatting.Indented);
                    File.WriteAllText(SettingsFileName, ayarlar);
                }
            }
            
            public void LoadTheSettings()
            {
                // SettingsFileName is the json text file path where it is stored
                string settings_text = File.ReadAllText(SettingsFileName);
                settings = JsonConvert.DeserializeObject<Dictionary<string, KeyValuePair<string, object>>>(settings_text);
                
                foreach (string settingValue in settings.Keys)
                    if (settings[settingValue].Value != null)
                        {
                            Type type = typeof(MyClass);
                            Type typeOfObject = Type.GetType(settings[settingValue].Key);
    
                            FieldInfo? fieldInfo = type.GetField(settingValue, BindingFlags.NonPublic | BindingFlags.Instance);
                            fieldInfo?.SetValue(this, ConvertToType(settings[settingValue].Value, typeOfObject));
                        }
            }
            
            object? ConvertToType(object value, Type valueType)
            {
                if (value != null && value.GetType() == valueType)
                    return value;
                if (!(value is JToken token))
                    token = JToken.FromObject(value!);
                return token.ToObject(valueType);
            }
        }