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.