使用自定义 iDefaultContractResolver 填充和合并来自 Json 的对象集合会导致数据移动/损坏
本文关键字:集合 对象 数据 损坏 移动 Json iDefaultContractResolver 自定义 填充 合并 | 更新日期: 2023-09-27 18:30:46
这是我正在序列化/反序列化的类数据结构:
public class SettingGroup
{
public string name { get; set; }
public string description { get; set; }
public bool visible { get; set; }
public ObservableCollection<SettingGroup> groups { get; set; }
public ObservableCollection<Setting> settings { get; set; }
public SettingGroup()
{
groups = new ObservableCollection<SettingGroup>();
settings = new ObservableCollection<Setting>();
visible = true;
}
}
public class Setting
{
public string name { get; set; }
public string description { get; set; }
public bool visible { get; set; }
public DescriptionVisibility descriptionVisibility { get; set; }
public Dictionary<string, dynamic> configuration { get; set; }
public dynamic settingValue { get; set; }
public SettingType settingType { get; set; }
public SettingControl settingControl { get; set; }
public Setting()
{
visible = true;
configuration = new Dictionary<string, dynamic>();
}
}
我使用以下方法来确保只有设置名称和值存储在 JSON 中,其余的在应用程序本身内结构化,不需要通过 JSON 保存/加载;
private static string safeFileName(string fileName)
{
string regexSearch = new string(Path.GetInvalidFileNameChars()) + " ";
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
fileName = r.Replace(fileName, "");
return fileName;
}
public static void saveSettings(this SettingGroup settingGroup, string fileName = "")
{
var jsonResolver = new IgnorableSerializerContractResolver();
jsonResolver.Ignore(typeof(SettingGroup), "visible");
jsonResolver.Ignore(typeof(SettingGroup), "description");
jsonResolver.Ignore(typeof(Setting), "visible");
jsonResolver.Ignore(typeof(Setting), "descriptionVisibility");
jsonResolver.Ignore(typeof(Setting), "configuration");
jsonResolver.Ignore(typeof(Setting), "settingType");
jsonResolver.Ignore(typeof(Setting), "settingControl");
jsonResolver.Ignore(typeof(Setting), "description");
var jsonSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, ContractResolver = jsonResolver };
if (string.IsNullOrWhiteSpace(fileName))
fileName = safeFileName(settingGroup.name);
try
{
if (!Directory.Exists(Global.DataDirectory)) Directory.CreateDirectory(Global.DataDirectory);
File.WriteAllText(Path.Combine(Global.DataDirectory, fileName+".json"), JsonConvert.SerializeObject(settingGroup, Newtonsoft.Json.Formatting.Indented, jsonSettings));
}
catch { }
}
public static void loadSettings(this SettingGroup settingGroup, string fileName = "")
{
var jsonResolver = new IgnorableSerializerContractResolver();
jsonResolver.Ignore(typeof(SettingGroup), "visible");
jsonResolver.Ignore(typeof(SettingGroup), "description");
jsonResolver.Ignore(typeof(Setting), "visible");
jsonResolver.Ignore(typeof(Setting), "descriptionVisibility");
jsonResolver.Ignore(typeof(Setting), "configuration");
jsonResolver.Ignore(typeof(Setting), "settingType");
jsonResolver.Ignore(typeof(Setting), "settingControl");
jsonResolver.Ignore(typeof(Setting), "description");
if (string.IsNullOrWhiteSpace(fileName))
fileName = safeFileName(settingGroup.name);
try
{
if (!Directory.Exists(Global.DataDirectory)) Directory.CreateDirectory(Global.DataDirectory);
var serializerSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Auto, ContractResolver = jsonResolver };
JsonConvert.PopulateObject(File.ReadAllText(Path.Combine(Global.DataDirectory, fileName + ".json")), settingGroup, serializerSettings);
}
catch { }
}
自定义 iDefaultContractResolver 是我在 SE 周围看到的,但为了完整性,无论如何都会包含在这里:
public class IgnorableSerializerContractResolver : DefaultContractResolver
{
protected readonly Dictionary<Type, HashSet<string>> Ignores;
public IgnorableSerializerContractResolver()
{
this.Ignores = new Dictionary<Type, HashSet<string>>();
}
public void Ignore(Type type, params string[] propertyName)
{
if (!this.Ignores.ContainsKey(type)) this.Ignores[type] = new HashSet<string>();
foreach (var prop in propertyName)
{
this.Ignores[type].Add(prop);
}
}
public bool IsIgnored(Type type, string propertyName)
{
if (!this.Ignores.ContainsKey(type)) return false;
// if no properties provided, ignore the type entirely
if (this.Ignores[type].Count == 0) return true;
return this.Ignores[type].Contains(propertyName);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (this.IsIgnored(property.DeclaringType, property.PropertyName)
// need to check basetype as well for EF -- @per comment by user576838
|| this.IsIgnored(property.DeclaringType.BaseType, property.PropertyName))
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
正如我所期望的那样,它保存得很好,只有我想要保存的属性,但是加载会导致数据"移动",所有设置值都将加载到错误的设置中,等等,并在尝试访问它们时导致类型不匹配。我已经尝试在加载部分使用和不使用自定义 Json 设置,行为没有差异。另外值得注意的是,如果没有 iDefaultContractResolver,整个结构可以很好地保存/加载,但 id 不会用不需要的数据弄乱我的 JSON 文件。
下面是正在构造和填充示例数据的设置类:
SettingGroup Settings = new SettingGroup();
Settings.name = "Application Settings";
Settings.description = "Common application settings.";
SettingGroup generalSettings = new SettingGroup();
generalSettings.name = "General settings";
Settings.groups.Add(generalSettings);
SettingGroup themeSettings = new SettingGroup();
themeSettings.name = "Theme settings";
Settings.groups.Add(themeSettings);
SettingGroup updateSettings = new SettingGroup();
updateSettings.name = "Update settings";
Settings.groups.Add(updateSettings);
SettingGroup startupSettings = new SettingGroup();
startupSettings.name = "Startup settings";
generalSettings.groups.Add(startupSettings);
Setting startWithWindows = new Setting();
startWithWindows.name = "Start IM with Windows";
startWithWindows.settingValue = true;
startWithWindows.settingControl = SettingControl.Checkbox;
startupSettings.settings.Add(startWithWindows);
Setting startMinimized = new Setting();
startMinimized.name = "Start IM minimized";
startMinimized.settingValue = true;
startMinimized.settingControl = SettingControl.Checkbox;
startupSettings.settings.Add(startMinimized);
SettingGroup performanceSettings = new SettingGroup();
performanceSettings.name = "Performance settings";
generalSettings.groups.Add(performanceSettings);
Setting threadPriority = new Setting();
threadPriority.name = "Thread priority";
threadPriority.description = "This setting may not have a noticeible impact on all platforms, especially higer end ones.";
threadPriority.settingValue = 3;
threadPriority.settingControl = SettingControl.Slider;
threadPriority.configuration.Add("lowVal",0);
threadPriority.configuration.Add("highVal", 7);
threadPriority.configuration.Add("interval", 1);
performanceSettings.settings.Add(threadPriority);
经过编辑以包含 Json 源和结果:
具有设置信息和默认值的完整 JSON 结构:
{
"name": "Application Settings",
"description": "Common application settings.",
"visible": true,
"groups": [
{
"name": "General settings",
"description": null,
"visible": true,
"groups": [
{
"name": "Startup settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Start IM with Windows",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
},
{
"name": "Start IM minimized",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
}
]
},
{
"name": "Performance settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Thread priority",
"description": "This setting may not have a noticeible impact on all platforms, especially higer end ones.",
"visible": true,
"descriptionVisibility": 0,
"configuration": {
"lowVal": 0,
"highVal": 7,
"interval": 1
},
"settingValue": 3,
"settingType": 0,
"settingControl": 2
}
]
}
],
"settings": []
},
{
"name": "Theme settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "Update settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "General settings",
"description": null,
"visible": true,
"groups": [
{
"name": "Startup settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Start IM with Windows",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
},
{
"name": "Start IM minimized",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
}
]
},
{
"name": "Performance settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Thread priority",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": 3,
"settingType": 0,
"settingControl": 0
}
]
}
],
"settings": []
},
{
"name": "Theme settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "Update settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
}
],
"settings": []
}
由我的 saveSettings 扩展保存的数据,该扩展旨在仅保存所需的信息,例如组名子项以及设置名称和值(这看起来正确)
{
"name": "Application Settings",
"groups": [
{
"name": "General settings",
"groups": [
{
"name": "Startup settings",
"groups": [],
"settings": [
{
"name": "Start IM with Windows",
"settingValue": true
},
{
"name": "Start IM minimized",
"settingValue": true
}
]
},
{
"name": "Performance settings",
"groups": [],
"settings": [
{
"name": "Thread priority",
"settingValue": 3
}
]
}
],
"settings": []
},
{
"name": "Theme settings",
"groups": [],
"settings": []
},
{
"name": "Update settings",
"groups": [],
"settings": []
}
],
"settings": []
}
但是,当加载并与现有类对象合并时,这就是结果数据的样子,请注意控件类型中的重复和无效值,这会导致类型不匹配 IE 无法将long
值加载到复选框使用的bool
中。
{
"name": "Application Settings",
"description": "Common application settings.",
"visible": true,
"groups": [
{
"name": "General settings",
"description": null,
"visible": true,
"groups": [
{
"name": "Startup settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Start IM with Windows",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
},
{
"name": "Start IM minimized",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
}
]
},
{
"name": "Performance settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Thread priority",
"description": "This setting may not have a noticeible impact on all platforms, especially higer end ones.",
"visible": true,
"descriptionVisibility": 0,
"configuration": {
"lowVal": 0,
"highVal": 7,
"interval": 1
},
"settingValue": 3,
"settingType": 0,
"settingControl": 2
}
]
}
],
"settings": []
},
{
"name": "Theme settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "Update settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "General settings",
"description": null,
"visible": true,
"groups": [
{
"name": "Startup settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Start IM with Windows",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
},
{
"name": "Start IM minimized",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": true,
"settingType": 0,
"settingControl": 0
}
]
},
{
"name": "Performance settings",
"description": null,
"visible": true,
"groups": [],
"settings": [
{
"name": "Thread priority",
"description": null,
"visible": true,
"descriptionVisibility": 0,
"configuration": {},
"settingValue": 3,
"settingType": 0,
"settingControl": 0
}
]
}
],
"settings": []
},
{
"name": "Theme settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
},
{
"name": "Update settings",
"description": null,
"visible": true,
"groups": [],
"settings": []
}
],
"settings": []
}
您应该能够使用KeyedIListMergeConverter
(不是专门用于合并List<T>
集合的原始KeyedListMergeConverter
)来自 Json.Net 填充对象 - 基于 ID 更新列表元素,并将[JsonMergeKey]
附加到名称:
public class SettingGroup
{
[JsonMergeKey]
public string name { get; set; }
// Remainder unchanged
}
public class Setting
{
[JsonMergeKey]
public string name { get; set; }
// Remainder unchanged
}
然后像这样使用它:
if (!Directory.Exists(Global.DataDirectory))
Directory.CreateDirectory(Global.DataDirectory);
var serializerSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Auto, ContractResolver = jsonResolver };
serializerSettings.Converters.Add(new KeyedIListMergeConverter(settings.ContractResolver));
JsonConvert.PopulateObject(File.ReadAllText(Path.Combine(Global.DataDirectory, fileName + ".json")), settingGroup, serializerSettings);