Reputation: 63
As part of my Xamarin Forms app, developed for Android, i have a Json config file wherein i save some settings, like whether or not the app is in debug mode or things like a User, used to log into a website.
However trying to get this User object from the Json file throws the mentioned exception, which can also be seen in full below.
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
The User and ConfigModel objects that i'm trying to deserialize into is quite simple id say. They can be seen below, along with a section of my Config file that contains some of my properties stored in the Json, followed by my 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"
}
The part of my code that does the blunt of the work, for this process, comes from my utility library. This library is a submodule on my github and the full one can be found here: https://github.com/andr9528/Wolf.Utility.Main
Beneath is the methods from my JsonManipulator class, which throws the exception mentioned when trying to Deserialize into the User object, i.e T is User. For the full class go to the link above.
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;
}
}
...
}
It is only the Get part of the User Property in my Config that has this issue, which is the one i need to call to get the User object containing the Username and Password.
To try and solve the issue i have made a number of changes, which can all be seen in the above code. Some examples, if not all, are as follows.
Added JsonProperty specifications to properties on the User object
Added a JsonConstructor on the User object
Changed the type of the property 'CreationTime' on the User object from DateTime to string.
Created the method ReadValueViaModel in JsonManipulator together with the ConfigModel class
I have that annoying felling that, what i'm missing is just a tiny piece of code or formatting somewhere, but i simply can't figure out where and what i'm missing for it to work.
Fell free to ask me questions to clear up any missing information.
EDIT 1: Updated Json formatting - Directly copied from the autogenerated json file. According to https://jsonlint.com/, it is valid Json, so my WriteValue Method is creating valid json.
Upvotes: 1
Views: 5850
Reputation: 1910
It would seem you are doing this because you are unaware of some of the features of the library. This is the one of the most commonly used libraries out there (to the point Microsoft includes it by default in most web based project types) - please look at the features of the library before driving yourself crazy trying to re-invent its features.
default(..)
value; null
for classes and default value for primitives (eg: 0
for numerics) This is not something that JSON.net is doing; the fields just get ignored, meaning they contain their default values.Here is a previous stack overflow question: Default value for missing properties with JSON.net
Here is a code example:
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; }
}
And here is a screenshot of the dump:
Update:
You've yet to give a good reason for doing things the way you're doing it. Instead of storing a JSON object that contains a stringified JSON object, you should be storing something like this:
{
"DebugMode": "true",
"UseCustomOptions": "false",
"SelectedMenu": "3",
"CustomFormats": [],
"User": {
"username": "Ole",
"password": "ole",
"verifiedStatus": "false",
"creationTime": "10-02-2020 13:35:13"
}
}
And your models should look like (obviously I'm guessing a bit here as you don't have all of your code posted):
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);
}
Upvotes: 1