skip to Main Content

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.

2

Answers


  1. Your FromDictionary() method needs two fixes:

    • When you do type.GetField(settingValue, ...) the type you use must be typeof(MyClass) not typeof(Dictionary<string, object>).

    • Before setting the field value, you must deserialize or convert the dictionary value to the type of the field.

    This can be done as follows:

    public static MyClass FromDictionary(Dictionary<string, object> settings)
    {
        var instance = new MyClass();
        var type = typeof(MyClass);
        
        foreach (var pair in settings)
        {
            var fieldInfo = type.GetField(pair.Key, BindingFlags.NonPublic | BindingFlags.Instance);
            // This does not work -- fieldInfo is null, and settings[settingValue] is not the correct type.
            if (fieldInfo == null)
                continue; // or throw an exception;
            fieldInfo.SetValue(instance, JsonExtensions.ConvertToType(pair.Value, fieldInfo.FieldType));
        }           
        
        return instance;
    }
    

    Using the following extension method:

    public static class JsonExtensions
    {
        public static 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);
        }
    }
    

    Demo fiddle #1 here.

    As an alternative, if you need to serialize and deserialize only specific (possibly private) fields of some type, you can do this directly with a custom contract resolver. Using one completely eliminates the need for some intermediate Dictionary<string, object> and so avoids the need to somehow guess the type of the value objects when deserializing.

    First define the following resolver:

    public class SelectedFieldsContractResolver : DefaultContractResolver
    {
        readonly Dictionary<Type, string []> typesAndFields;
        
        public SelectedFieldsContractResolver(IEnumerable<KeyValuePair<Type, string []>> typesAndFields) =>
            (this.typesAndFields) = typesAndFields.ToDictionary(p => p.Key, p => p.Value);
    
        protected override List<MemberInfo> GetSerializableMembers(Type objectType) => 
            objectType switch
            {
                var t when typesAndFields.TryGetValue(t, out var fields) => fields.Select(f => (MemberInfo)objectType.GetField(f, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!).Where(f => f != null).ToList(),
                _ => base.GetSerializableMembers(objectType),
            };
            
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            if (member is FieldInfo f && f.ReflectedType != null && typesAndFields.ContainsKey(f.ReflectedType))
                property.Readable = property.Writable = true;
            return property;
        }
    }
    

    Then, modify MyClass as follows:

    public partial class MyClass
    {
        static Lazy<JsonSerializerSettings> settings = 
            new(() =>
                new JsonSerializerSettings ()
                {
                    ContractResolver = 
                        new SelectedFieldsContractResolver(
                            new KeyValuePair<Type, string []> [] 
                            {
                                new(typeof(MyClass), 
                                    new [] { nameof(SomeParameters), nameof(notifications), nameof(someKey), nameof(someNumber), nameof(aChar) })
                            }),
                });
        
        public static MyClass FromSelectedFieldsJson(string json) => JsonConvert.DeserializeObject<MyClass>(json, settings.Value);
        
        public string ToSelectedFieldsJson(Formatting formatting = Formatting.None) => JsonConvert.SerializeObject(this, formatting, settings.Value);
    

    And now you will be able to round-trip your selected fields to and from JSON as follows:

    var json = myClass.ToSelectedFieldsJson(Formatting.Indented);
    
    var myClass2 = MyClass.FromSelectedFieldsJson(json);
    

    Demo fiddle #2 here.

    Login or Signup to reply.
  2. your code is very slopy. Just use this FromDictionary code

    public static MyClass FromDictionary(Dictionary<string, object> settings)
    {
        return  JObject.FromObject(settings).ToObject<MyClass>();
    }
    

    and mark your constructor with parameters as [JsonConstructor]

    [JsonConstructor]
    public MyClass(Dictionary<string, Parameters> someParameters, NotificationList notifications, string someKey, int someNumber, char aChar)
    

    and IMHO

    public class NotificationList : List<string> { }
    

    I have never seen any good from collection inheritance

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search