如何使用JSON.NET从JSON和JSON中只[序列化]一种类型的选定私有字段?

yhqotfr8  于 2023-08-08  发布在  .NET
关注(0)|答案(3)|浏览(99)

我有一个类型,我只需要在JSON和该类型的选定私有字段之间来回传递,忽略所有其他字段和属性,无论是公共的还是私有的。如何使用Json.NET实现这一点?
我目前的方法是使用一个中间的Dictionary<string, object>。序列化有效,但反序列化无效

  • 对于序列化,我向我的类型添加了一个方法,该方法构造一个Dictionary<string, object>,并按名称用必需的字段填充它。后来,我把字典序列化了。

这个很管用。

  • 对于反序列化,我将反序列化为Dictionary<string, object>,然后尝试使用反射按名称和值填充我的类型的字段。
    这不起作用,因为字典中的object值的类型错误。

我该如何解决此问题?
范例类别:

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> { }

字符串
用于创建我的字典并从字典中重新创建MyClass的示例类方法:

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;
    }
}


JSON示例:

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


所以key(string)是我的字段名,object是字段值本身。序列化没有问题,但是我无法将object(值)反序列化回原始类型。我找了很多,找不到任何简单的解决办法。
注意:我不能将这些变量放在类中并反序列化回去。特别是我需要在那个类里面做这个操作,并把值设置回去。必须分别为每个字段执行此操作。
演示小提琴here

x6492ojm

x6492ojm1#

您的FromDictionary()方法需要两个修复:

  • 当您执行type.GetField(settingValue, ...)时,您使用的类型必须是typeof(MyClass),而不是typeof(Dictionary<string, object>)
  • 在设置字段值之前,必须将字典值反序列化或转换为字段的类型。

这可以通过以下方式完成:

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;
}

字符串
使用以下扩展方法:

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);
    }
}


演示小提琴#1 here

作为替代方案,如果您只需要序列化和反序列化某些类型的特定(可能是私有)字段,您可以直接使用自定义合约解析器来完成此操作。使用一个完全消除了对一些中间Dictionary<string, object>的需要,因此避免了在反序列化时以某种方式猜测值对象的类型的需要。

首先定义以下解析器:

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;
    }
}


然后,修改MyClass如下:

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);


现在,您将能够在所选字段与JSON之间进行往返,如下所示:

var json = myClass.ToSelectedFieldsJson(Formatting.Indented);

var myClass2 = MyClass.FromSelectedFieldsJson(json);


演示小提琴#2 here

balp4ylt

balp4ylt2#

你的代码很马虎。只要使用这个FromDictionary代码

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

字符串
并将带有参数的构造函数标记为[JsonConstructor]

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


IMHO

public class NotificationList : List<string> { }


我从来没有从收藏继承中看到过什么好的

j7dteeu8

j7dteeu83#

首先感谢dbc提供了很大的帮助。我从他的回答中更新了我的需求。
首先,我简短地问“我如何反序列化并设置回MyClass的示例”,但设置回示例不起作用。所以我稍微改变了一下方式。现在我不反序列化到示例,而是通过this将巴克反序列化到类示例本身。
这是我需要[反]序列化的类。SaveSettings将参数发送到它的重载方法。重载方法接受参数并添加字段名称,字段类型(字段类型转换为字符串表示)和最后字段的值到settings字典中,其中包含所有字段变量。
添加所有字段/属性后,唯一要做的就是序列化并保存到磁盘。
当涉及到反序列化时,字段的名称,它的类型和字段值(内容)被设置回类内部,如下所示:
fieldInfo?.SetValue(this,ConvertToType(settings[settingValue].Value,typeOfObject))
我从dbc中获取了ConvertToType方法
我通过以下方式来确定磁场类型
Type.GetType(settings[settingValue].Key);
这可以检索存储在磁盘中的字符串类型。(简单地说,字符串到类型,类型到字符串)

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);
        }
    }

字符串

相关问题