【发布时间】:2020-02-10 14:17:21
【问题描述】:
作为我为 Android 开发的 Xamarin Forms 应用程序的一部分,我有一个 Json 配置文件,其中我保存了一些设置,例如应用程序是否处于调试模式或用户等用于登录网站的设置。
但是,尝试从 Json 文件中获取此 User 对象会引发上述异常,也可以在下面完整查看。
EXCEPTION 10-02-2020 14:06:08 Newtonsoft.Json.JsonSerializationException => Error converting value "{
"$type": "Dental.App.Models.User, Dental.App",
"username": "Ole",
"password": "ole",
"verifiedStatus": false,
"creationTime": "10-02-2020 13:35:13"
}" to type 'Dental.App.Models.User'. Path 'User', line 5, position 197.; Stacktrace => at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType (Newtonsoft.Json.JsonReader reader, System.Object value, System.Globalization.CultureInfo culture, Newtonsoft.Json.Serialization.JsonContract contract, System.Type targetType) [0x000bd] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x000d7] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00061] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00267] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00154] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x000d9] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00053] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x0002d] in <f393073e86a643d9809b1a5d0c498495>:0
at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <f393073e86a643d9809b1a5d0c498495>:0
at Wolf.Utility.Main.Transport.JsonManipulator.ReadValueViaModel[T,U] (System.String path, System.String propertyName) [0x000c8] in <46eeb80ce9a440109e5bc07b0f1af244>:0
at Dental.App.Config.get_User () [0x00013] in <c94e9f8f60b14ec69bd9794bdf834717>:0
at Dental.App.Views.DentalWebPage.DentalWebView_LoadFinished (System.Object sender, System.EventArgs e) [0x00035] in <c94e9f8f60b14ec69bd9794bdf834717>:0
我试图反序列化的 User 和 ConfigModel 对象是非常简单的 id 说。它们可以在下面看到,以及我的配置文件的一部分,其中包含我存储在 Json 中的一些属性,然后是我的 Json。
public class User
{
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
[JsonProperty("verifiedStatus")]
public bool VerifiedStatus { get; set; }
[JsonProperty("creationTime")]
public string CreationTime { get; private set; }
[JsonIgnore]
public DateTime TimeOfCreation => Convert.ToDateTime(CreationTime);
public User()
{
CreationTime = DateTime.Now.ToString(CultureInfo.CurrentCulture);
}
[JsonConstructor]
public User(string creationTime)
{
CreationTime = creationTime;
}
}
public class ConfigModel
{
public User User { get; set; }
}
public class Config
{
private static User user = new User();
public static User User
{
get => CanSave ? JsonManipulator.ReadValueViaModel<User, ConfigModel>(ConfigPath, nameof(User)) : user;
set
{
if (CanSave) JsonManipulator.WriteValue(ConfigPath, nameof(User), value);
else user = value;
}
}
private static bool debugMode = true;
public static bool DebugMode
{
get => CanSave ? JsonManipulator.ReadValue<bool>(ConfigPath, nameof(DebugMode)) : debugMode;
set
{
if (CanSave) JsonManipulator.WriteValue(ConfigPath, nameof(DebugMode), value);
else debugMode = value;
}
}
...
}
{
"DebugMode": "true",
"UseCustomOptions": "false",
"CustomFormats": "{\n \"$type\": \"System.Collections.Generic.List`1[[ZXing.BarcodeFormat, zxing.portable]], mscorlib\",\n \"$values\": []\n}",
"User": "{\n \"$type\": \"Dental.App.Models.User, Dental.App\",\n \"username\": \"Ole\",\n \"password\": \"ole\",\n \"verifiedStatus\": false,\n \"creationTime\": \"10-02-2020 13:35:13\"\n}",
"SelectedMenu": "3"
}
对于这个过程,我的代码中完成这项工作的部分来自我的实用程序库。这个库是我 github 上的一个子模块,完整的可以在这里找到:https://github.com/andr9528/Wolf.Utility.Main
下面是我的 JsonManipulator 类中的方法,它在尝试反序列化到用户对象时抛出提到的异常,即 T 是用户。如需完整课程,请访问上面的链接。
public class JsonManipulator
{
/// <summary>
/// Parses Json file, and returns the attribute specified by 'propertyName'.
/// </summary>
/// <typeparam name="T">The type returned, from the property named after the input 'propertyName'.</typeparam>
/// <param name="path">Path of the Json file.</param>
/// <param name="propertyName">Name of the property to return.</param>
/// <returns></returns>
public static T ReadValue<T>(string path, string propertyName)
{
if (!File.Exists(path))
throw new ArgumentNullException(nameof(path), $@"No file Exist on the specified path => {path}");
if (path.Split('.').Last().ToLowerInvariant() != "json")
throw new ArgumentException("The path given did not end in 'json'");
var json = File.ReadAllText(path);
try
{
var obj = JObject.Parse(json);
if (obj != null)
return obj[propertyName].ToObject<T>();
throw new OperationFailedException($"Failed to parse Json from the file located at => {path}");
}
catch (Exception ex)
{
throw;
}
}
/// <summary>
/// Deserializes Json file into specified model, and returns the property from it by the value specified in 'propertyName'.
/// </summary>
/// <typeparam name="T">The type returned, from the property named after the input 'propertyName'.</typeparam>
/// <typeparam name="U">The model that is deserialized into, and from which the property is taken and returned.</typeparam>
/// <param name="path">Path of the Json file.</param>
/// <param name="propertyName">Name of the property to return.</param>
/// <returns></returns>
public static T ReadValueViaModel<T, U>(string path, string propertyName)
{
if (!File.Exists(path))
throw new ArgumentNullException(nameof(path), $@"No file Exist on the specified path => {path}");
if (path.Split('.').Last().ToLowerInvariant() != "json")
throw new ArgumentException("The path given did not end in 'json'");
var json = File.ReadAllText(path);
try
{
var obj = JsonConvert.DeserializeObject<U>(json, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.All
});
var prop = obj.GetType().GetProperties().First(x => x.Name == propertyName);
return (T)prop.GetValue(obj);
}
catch (Exception)
{
throw;
}
}
...
}
只有我的配置中的用户属性的获取部分存在此问题,这是我需要调用以获取包含用户名和密码的用户对象的部分。
为了尝试解决这个问题,我做了一些更改,这些都可以在上面的代码中看到。以下是一些示例(如果不是全部)。
为 User 对象的属性添加了 JsonProperty 规范
在 User 对象上添加了 JsonConstructor
将 User 对象上的属性“CreationTime”的类型从 DateTime 更改为字符串。
在 JsonManipulator 中与 ConfigModel 类一起创建了 ReadValueViaModel 方法
我有一种恼人的感觉,我缺少的只是一小段代码或某处的格式,但我根本无法弄清楚我在哪里以及缺少什么才能让它工作。
随时问我问题以清除任何缺失的信息。
编辑 1:更新 Json 格式 - 直接从自动生成的 json 文件复制。根据https://jsonlint.com/,它是有效的Json,所以我的WriteValue方法正在创建有效的json。
【问题讨论】:
-
为什么JSON里面有JSON?好像你把这个复杂化了。
-
我能问一下你为什么使用这个 JsonManipulator 吗? Newtonsoft.Json 的卖点是序列化一个对象,即使是复杂的集合,你所要做的就是
JsonConvert.SerializeObject(myConfig);,反序列化你所要做的就是var myConfig = JsonConvert.DeserializeObject<ConfigModel>(inputString);。我建议您只反序列化整个配置对象并使用它,而不是尝试创建自己的操纵器。在我看来,您放弃了与图书馆合作的许多优势。只需将所有字段添加到ConfigModel -
@MichaelJones JsonManipulator 是我自己创建的,它确实使用了 JsonConvert SerializeObject 和 DeserializeObject 方法。查看我的 github 以获取完整的课程。使用上面显示的第二种读取方法可能是用于获取数据的最佳方法,因为它可以反序列化到我的模型中,然后返回所需的值。另一方面,序列化孔模型并保存它,不允许我在 json 中检查/添加缺失的属性,而不会丢失保存的设置。我在添加到 json 的每一行上都使用了 SerializeObject 方法,因此它的格式应该正确。
-
@AndréMadsen 我仍然不明白为什么需要单独的操纵器 - 你能提供一个例子来说明它的目的是什么吗?我看不出你在做什么有什么好处。相反,它似乎使库的使用过于复杂。
-
@MichaelJones 假设我们有一个应用程序,其 Json 中存储了 5 个值。我们不想丢失这些值,以防万一出现需要向 json 文件添加新值的新版本。除非我们在通过模型更新 json 文件之前,获取并读取现有数据并将其复制过来,否则设置将重置为默认值。 json 必须包含至少一个默认值的新值,以防止读取错误。在我的类中使用我的 Write 方法(请参阅 github 链接),我可以简单地使用默认值添加新值,而无需先复制旧数据。
标签: c# json serialization xamarin.forms