【问题标题】:Getting this exception "Newtonsoft.Json.JsonSerializationException", when trying to deserialize Json to object尝试将 Json 反序列化为对象时出现此异常“Newtonsoft.Json.JsonSerializationException”
【发布时间】: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;
            }
        }
...
}

只有我的配置中的用户属性的获取部分存在此问题,这是我需要调用以获取包含用户名和密码的用户对象的部分。

为了尝试解决这个问题,我做了一些更改,这些都可以在上面的代码中看到。以下是一些示例(如果不是全部)。

  1. 为 User 对象的属性添加了 JsonProperty 规范

  2. 在 User 对象上添加了 JsonConstructor

  3. 将 User 对象上的属性“CreationTime”的类型从 DateTime 更改为字符串。

  4. 在 JsonManipulator 中与 ConfigModel 类一起创建了 ReadValueViaModel 方法

我有一种恼人的感觉,我缺少的只是一小段代码或某处的格式,但我根本无法弄清楚我在哪里以及缺少什么才能让它工作。

随时问我问题以清除任何缺失的信息。

编辑 1:更新 Json 格式 - 直接从自动生成的 json 文件复制。根据https://jsonlint.com/,它是有效的Json,所以我的WriteValue方法正在创建有效的json。

【问题讨论】:

  • 为什么JSON里面有JSON?好像你把这个复杂化了。
  • 我能问一下你为什么使用这个 JsonManipulator 吗? Newtonsoft.Json 的卖点是序列化一个对象,即使是复杂的集合,你所要做的就是JsonConvert.SerializeObject(myConfig);,反序列化你所要做的就是var myConfig = JsonConvert.DeserializeObject&lt;ConfigModel&gt;(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


【解决方案1】:

您这样做似乎是因为您不了解该库的某些功能。这是最常用的库之一(Microsoft 在大多数基于 Web 的项目类型中默认包含它) - 请先查看库的功能,然后再让自己疯狂地尝试重新发明它的功能。

  • 如果您向模型中不存在的 JSON 添加新字段,则会被忽略
  • 如果您的模型中有一个项目在 JSON 中丢失,则使用其 default(..) 值填充它; null 用于类和原始值的默认值(例如:0 用于数字) 这不是 JSON.net 正在做的事情;这些字段会被忽略,这意味着它们包含它们的默认值。
  • 如果您想添加带有默认值的新字段,则支持。

这是之前的堆栈溢出问题:Default value for missing properties with JSON.net

这是一个代码示例:

void Main()
{
    string testJSON = @"[{""FirstName"":""Michael"",""LastName"":""Jones""},{""FirstName"":""Jon"",""LastName"":""Smith""}]";
    PersonModel[] people = JsonConvert.DeserializeObject<PersonModel[]>(testJSON);
    people.Dump();
}

public class PersonModel
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GUID { get; set; }

    [DefaultValue(100)] //lets be generous when starting our loyalty program!
    [JsonProperty("LoyaltyPoints", DefaultValueHandling = DefaultValueHandling.Populate)]
    public int LoyaltyPoints { get; set; }
}

这是转储的屏幕截图:


更新:

你还没有给出一个很好的理由来做你正在做的事情。您应该存储这样的内容,而不是存储包含字符串化 JSON 对象的 JSON 对象:

{
    "DebugMode": "true",
    "UseCustomOptions": "false",
    "SelectedMenu": "3",
    "CustomFormats": [],
    "User": {
        "username": "Ole",
        "password": "ole",
        "verifiedStatus": "false",
        "creationTime": "10-02-2020 13:35:13"
    }
}

你的模型应该是这样的(显然我在这里猜测了一下,因为你没有发布所有代码):

public class Config
{
    public bool DebugMode { get; set; }
    public bool UseCustomOptions { get; set; }
    public int SelectedMenu { get; set; }
    public List<ZXing.BarcodeFormat> CustomFormats { get; set; }
    public User User { get; set; }
}

public class User 
{
    public string Username { get; set; }
    public string Password { get; set; }
    public bool VerifiedStatus { get; set; }
    public DateTime CreationTime { get; set; }
}

void Main()
{
    //and then you can just ...
    string myJsonString = "...";
    Config config = JsonConvert.DeserializeObject<Config>(myJsonString);
}

【讨论】:

  • Michael,我的问题不是写 Json,我的写作方法很好。我的问题是阅读 Json。 Netiher JObejct.ToObject 或 JsonConvert.DeserializeObject 能够解析 Json 的 User 部分中包含的值,抛出我要求解决的异常。问题中提供的 Json 是完全有效的 Json,因为它在获取 DebugMode、SelecetedMenu 或 UseCustomOptions 的值时运行良好。
  • @AndréMadsen 没错,它是有效的 JSON,但是通过在 JSON 对象中嵌入一个字符串化的 JSON 对象,然后尝试解析它,这使事情变得过于复杂。你不需要这样做——如果你只存储一个标准的 JSON 对象,它就可以正常工作。我仍然看不出使用您的JsonManipulator 并以这种方式做事的充分理由。如果您只使用内置的 SerializeObject(..) 方法,我已经用您的 JSON 存储时的样子更新了我的答案。
  • @AndréMadsen 它抛出异常的原因是 User 的值不是一个有效的 JSON 对象,而是一个字符串。如果您在提取 User 的值时将其更改为 JObject.ToObject&lt;string&gt;,我打赌它会起作用,因为那是您存储的内容 - 一个字符串。
  • 我觉得有点愚蠢......你在我的 json 中的用户是一个字符串的地方是正确的。重写了我的 write 方法来获取一个模型,它要么保存所有模型,要么将定义的属性复制过来。一般来说,它只是保存模型,因为输入的模型应该只是从 ReadModel 获得的模型,可能已经做了一些更改。
  • @AndréMadsen 我仍然敦促您考虑尝试按原样使用该库。您使用 JsonManipulator 创建的功能已经是库的一部分。通过避免创建像这样的不必要的帮助程序,您可以使您的代码更容易为未来的开发人员工作,并且更新库不太可能破坏事情。与图书馆合作而不是反对它。
猜你喜欢
  • 1970-01-01
  • 2020-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
相关资源
最近更新 更多