In my WPF code, I'm using Newtonsoft.Json to deserialize json into my models. First, I receive a Json string ('json') which I then parse into 'message'. (The object I want to deserialize is wrapped in a "data" field in the json string).
Activity message = JObject.Parse(json)["data"].ToObject<Activity>();
My Activity class uses several [JsonProperty] attributes to generate its fields. One of them is an enum called 'ActivityType'.
[JsonProperty("type")]
[JsonConverter(typeof(ActivityTypeConverter))]
public ActivityType Type { get; set; }
public enum ActivityType {
EmailOpen,
LinkClick,
Salesforce,
Unsupported
};
public class ActivityTypeConverter : JsonConverter {
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var rawString = existingValue.ToString().ToLower();
if (rawString.Contains("click"))
return ActivityType.LinkClick;
else if (rawString.Contains("salesforce"))
return ActivityType.Salesforce;
else if (rawString.Contains("email_open"))
return ActivityType.EmailOpen;
else
{
Console.WriteLine("unsupported " + rawString);
return ActivityType.Unsupported;
}
}
public override bool CanConvert(Type objectType)
{
return !objectType.Equals(typeof(ActivityType));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What's bizarre and frustrating is that json objects which I know have "type":"email_open" are being deserialized as ActivityType.Unsupported, even though my converter should be deserializing them as EmailOpen.
Debugging has shown what the problem is:
the json field "type" is automatically deserializing "email_open" as EmailOpen and then it is sent through my converter. (It breaks then because my conditional checks for an underscore, while EmailOpen.ToString() doesn't have one.)
So my question then is: Why is it converting without my converter and how do I stop it? I just want it to only use my converter
I think your converter is being called -- it's just not working. The problem is that, rather than reading the new value from the JsonReader reader, you are using the value from the existingValue. But this second value is the pre-existing property value in the class being deserialized, not the value being read.
You need to load the value from the reader along the lines of Json.NET's StringEnumConverter. Here's a version that does that and also handles standard values of your enum by subclassing StringEnumConverter and passing the value read from the file to the base class for further processing:
public class ActivityTypeConverter : StringEnumConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
bool isNullable = (Nullable.GetUnderlyingType(objectType) != null);
Type type = (Nullable.GetUnderlyingType(objectType) ?? objectType);
if (reader.TokenType == JsonToken.Null)
{
if (!isNullable)
throw new JsonSerializationException();
return null;
}
var token = JToken.Load(reader);
if (token.Type == JTokenType.String)
{
var rawString = ((string)token).ToLower();
if (rawString.Contains("click"))
return ActivityType.LinkClick;
else if (rawString.Contains("salesforce"))
return ActivityType.Salesforce;
else if (rawString.Contains("email_open"))
return ActivityType.EmailOpen;
}
using (var subReader = token.CreateReader())
{
while (subReader.TokenType == JsonToken.None)
subReader.Read();
try
{
return base.ReadJson(subReader, objectType, existingValue, serializer); // Use base class to convert
}
catch (Exception ex)
{
return ActivityType.Unsupported;
}
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ActivityType);
}
}
Related
Just wondering if anyone knows of the best way to upgrade a JSON structure Deserialisation to a new class type.
To further explain the legacy value was
public string author;
this has now been updated in the api to the following
public class Author
{
public string name;
public string email;
public string url;
}
public Author author;
So now I have an issue where any legacy data does not deserialize into this correctly as it used to be a string and now its a class.
My current solution is if it fails to deserialize then to do it into a class that had the old structure and then use this to go into the new one, but i feel there must be a better way to cast the old sting value into the new class value as part of the process.
Thanks
EDIT-1:
Ok i have made some headway with a converter below
public class AuthorConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Author user = (Author)value;
writer.WriteValue(user);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Author author = new Author();
Debug.Log(objectType + " " + reader.Value);
if (reader.TokenType == JsonToken.String)
{
author.name = (string) reader.Value;
}
else if(reader.TokenType == JsonToken.StartObject)
{
try
{
JObject jObject = JObject.Load(reader);
if (jObject.TryGetValue("name", out JToken name))
{
author.name = name.Value<string>();
}
if (jObject.TryGetValue("email", out JToken email))
{
author.email = email.Value<string>();
}
if (jObject.TryGetValue("url", out JToken url))
{
author.url = url.Value<string>();
}
}
catch (Exception e)
{
UnityEngine.Debug.Log(e);
throw;
}
}
return author;
}
Appears to all be working, but feels a bit fiddly to have to get the values 1 by 1 and convert over, i tried using the jObject.ToObject method but appeared to cause an infinite loop. Either way its working, but im sure there is a better way, so still open for ideas.
Since your Author has a default (parameterless) constructor, you can avoid having to populate each property manually by using JsonSerializer.Populate(JsonReader, Object). And to avoid having to write your own WriteJson() method, override CanWrite and return false.
Thus your converter can be rewritten as follows:
public class AuthorConverter : JsonConverter
{
public override bool CanConvert(Type objectType) { return objectType == typeof(Author); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null; // Or throw an exception if you don't want to allow null.
else if (reader.TokenType == JsonToken.String)
return new Author { name = (string) reader.Value };
else
{
var author = new Author();
serializer.Populate(reader, author);
return author;
}
}
catch (Exception ex)
{
UnityEngine.Debug.Log(ex);
throw;
}
}
}
public static partial class JsonExtensions
{
// Skip past any comments to the next content token, asserting that the file was not truncated.
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
// Read (advance to) the next token, asserting that the file was not truncated.
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Notes:
Json.NET supports comments in JSON despite the fact that they are not part of the JSON standard. The serializer ignores comments but they must be manually skipped when writing a converter.
JsonReader will throw an exception on most types of malformed JSON (e.g {"a":"b"]) but will not throw an exception for a truncated file, so converters should not assume that expected tokens are read successfully.
See also Json.NET custom serialization with JsonConverter - how to get the "default" behavior and How to use default serialization in a custom JsonConverter.
Demo fiddle here.
I have a requirement for some deserialization I'm trying to handle where I could have these potential inputs:
{
"value": "a string"
}
-- or --
{
"value": {
"text": "a string"
// there are other properties, but for successful deserialization I only need text present
}
}
And I expect it to be able to successfully convert to the object, MyObject:
public class MyObject
{
[JsonProperty("text")
public string Text { get; set; }
}
So far this is what I have in my converter. This case works fine when it's a string (although not very efficient because I'm throwing an exception to catch a failed deserialization). However, when it's an object the reader throws an exception and I'm uncertain of how to handle it.
public class MyObjectConverter : JsonConverter
{
public override bool CanWrite { get => false; }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string) || objectType == typeof(MyObject);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
try
{
return JObject.Parse(value).ToObject<MyObject>();
}
catch (Exception)
{
return new MyObject
{
Text = value
};
}
}
}
perhaps I'm there is already a nice way to do this that I'm unaware of? If not, how can I determine whether my input is a string or an object to be able to return the object I care about?
SOLUTION:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
return new MyObject
{
Text = reader.Value?.ToString()
};
}
else if (reader.TokenType == JsonToken.StartObject)
{
JObject obj = JObject.Load(reader);
return obj.ToObject<MyObject>();
}
else
{
return null;
}
}
Your Object isn't structured like the input your expecting. For the second case, MyObject would need to look like this:
// this is ugly
public class MyObject
{
public Value value{get;set;}
public class Value{
public string text {get;set;}
}
}
If you want to have just an object with a single Text property like you currently do, you could do something like this:
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
var value = obj["value"];
if(value is JObject) // this will be true if the value property is a nested structure
return new MyObject(){Text=value["text"]}; // could also do value.ToObject<MyObject>() if you need more properties
else
return new MyObject(){Text=value};
}
I have some data stored as Json. One property in the data is either an integer (legacy data) like so:
"Difficulty": 2,
Or a complete object (new versions):
"Difficulty": {
"$id": "625",
"CombatModifier": 2,
"Name": "Normal",
"StartingFunds": {
"$id": "626",
"Value": 2000.0
},
"Dwarves": [
"Miner",
"Miner",
"Miner",
"Crafter"
]
},
I am trying to write a custom converter for the type that allows deserialization of both versions.
This is C#, using the latest version of newtonsoft.json.
I've written a converter, and deserializing the integer format is trivial - it's only the mix that is causing me trouble. The only way I can think to check is to try and fail; but this appears to leave the reader in an unrecoverable state. Also, calling deserialize in the catch block leads to an infinite loop.
public class DifficultyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
var jObject = serializer.Deserialize<JValue>(reader);
if (jObject.Value is Int32 intv)
return Library.EnumerateDifficulties().FirstOrDefault(d => d.CombatModifier == intv);
else
return null;
}
catch (Exception e)
{
return serializer.Deserialize<Difficulty>(reader);
}
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Difficulty);
}
}
Ideally I would be able to serialize into the new format always, and still support reading both formats. A couple of other options include:
Creating another serializer object that does not include the custom converter and calling it from the catch block.
Detecting out of date files at load and modifying the text before attempting to deserialize.
Kind of want to avoid those tho.
You have a couple of problems here:
You are getting an infinite recursion in calls to ReadJson() because your converter is registered with the serializer you are using to do the nested deserialization, either through settings or by directly applying [JsonConverter(typeof(DifficultyConverter))] to Difficulty.
The standard solution to avoid this is to manually allocate your Difficulty and then use serializer.Populate() to deserialize its members (e.g. as shown in this answer to Json.NET custom serialization with JsonConverter - how to get the "default" behavior) -- but you are also using PreserveReferencesHandling.Objects, which does not work with this approach.
What does work with reference preservation is to adopt the approach from this answer to JSON.Net throws StackOverflowException when using [JsonConvert()] and deserialize to some DTO that contains a property of type Difficulty which has a superseding converter applied directly to the property.
serializer.Deserialize<JValue>(reader); may advance the reader past the current token. This will cause the later attempt to deserialize as an object to fail.
Instead, just check the JsonReader.TokenType or preload into a JToken and check the Type.
Putting the above together, your converter should look like the following:
public class DifficultyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Null:
return null;
case JTokenType.Integer:
{
var intv = (int)token;
return Library.EnumerateDifficulties().FirstOrDefault(d => d.CombatModifier == intv);
}
case JTokenType.Object:
return token.DefaultToObject(objectType, serializer);
default:
throw new JsonSerializationException(string.Format("Unknown token {0}", token.Type));
}
}
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(Difficulty);
}
Using the following extension methods:
public static partial class JsonExtensions
{
public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;
var dtoToken = new JObject(new JProperty(nameof(DefaultSerializationDTO<object>.Value), token));
var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);
if (oldParent == null)
token.RemoveFromLowestPossibleParent();
return dto == null ? null : dto.GetValue();
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
interface IHasValue
{
object GetValue();
}
[JsonObject(NamingStrategyType = typeof(Newtonsoft.Json.Serialization.DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) { this.Value = value; }
public DefaultSerializationDTO() { }
[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }
public object GetValue() => Value;
}
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
// By https://stackoverflow.com/users/3744182/dbc
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}
Demo fiddle here.
EDIT: Clarify question:
I have overridden the JsonConverter for a base type (by applying [JsonConverter(typeof(TConverter))] to the superclass), but when deserializing the sub-type directly I want to use STANDARD serialization (i.e. no custom converter) for deserializing my derived object. How do I specify STANDARD serialization for use in the deserialize method, as if I had NOT overridden the JsonConverter?
I am using elastic search and can't call JsonConvert.DeserializeObject with my custom implementation of JsonConverter, and have to rely on the attribute for Elastic to use my converter.
However, using this converter as attribute seems to affect all sub classes as well, but I just want them to use the standard converter, so that I don't have to implement JsonConverter for each of many implementations.
This is my classes/logic as I would like it to look:
[Route("test")]
[HttpPost]
public HttpResponseMessage Test([FromBody] JToken json)
{
var res = json.ToObject<Product>(); // I want an object of ProductImpl type here
return Request.CreateResponse(res);
}
[JsonConverter(typeof(JsonProductConverted))]
public abstract class Product
{
}
public class ProductImpl : Product
{
}
public class JsonProductConverted : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject json = JObject.Load(reader);
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
var type = typeof(ProductImpl);
// var res = JsonConvert.DeserializeObject(json.ToString(), type, DEFAULT_JSONCONVERTER_HERE);
var res = DeserializeToObjectWithStandardJsonConverter(json, type);
return res;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
If I don't supply the default JsonConverter, or similar it will just use the JsonProductConverted converter, which creates an infinite loop.
Since you have added [JsonConverter(typeof(JsonProductConverted))] directly to your Product type, you could add a dummy converter to ProductImpl that returns false from CanRead and CanWrite:
[JsonConverter(typeof(NoConverter))]
public class ProductImpl : Product
{
}
public class NoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This overrides the base class's converter and then falls back on default serialization for both reading and writing
Sample .Net fiddle.
Another option would be to use serializer.Populate(). This avoids the call to the converter for the object itself:
public class JsonProductConverted : JsonTypeInferringConverterBase
{
protected override Type InferType(Type objectType, JObject json)
{
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
return typeof(ProductImpl);
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
public abstract class JsonTypeInferringConverterBase : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected abstract Type InferType(Type objectType, JObject json);
protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(actualType);
return contract.DefaultCreator();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var json = JObject.Load(reader);
var actualType = InferType(objectType, json);
// Construct object (or reuse existingValue if compatible)
if (existingValue == null || !actualType.IsAssignableFrom(existingValue.GetType()))
{
existingValue = CreateObject(actualType, serializer, json);
}
// Populate object.
using (var subReader = json.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
}
Note that the concrete objects must have parameterless constructors for this to work. If not, you can override protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json) and manually invoke a parameterized constructor by deserializing select properties inside the JObject json.
Sample fiddle #2.
I am getting an error when I try to return a list of nullable boolean from my JSON interceptor.
The interceptor attribute is:
[JsonConverter(typeof(NullableBoolListDeSerializer))]
public List<bool?> ExemptBenefits { get; set; }
The ReadJSON method on the interceptor is:
List<bool?> result = new List<bool?>();
(reader as Newtonsoft.Json.Linq.JTokenReader).CurrentToken.ToList().ForEach(item =>
{
string value = (String)item.ToObject(typeof(String));
switch (value.ToLower())
{
case "true":
case "yes":
case "1":
result.Add(true);
break;
case "false":
case "no":
case "0":
default:
result.Add(false);
break;
}
});
return result;
The JSON being submitted is:
{
"exemptBenefits": [
"1"
],
"_apiEndpoint": "/benefits/loan"
}
The error I am getting is:
Unexpected token when deserializing object: String. Path 'exemptBenefits[0]', line 1, position 187.
Wondering how to convert a list of strings (eg "1","0"."true", "false") from JSON to a List (true,false,true,false) in a JSON interceptor
(actually it is NewtonSoft.Json)
If you want to convert a list of string values into a list of nullable boolean values with a JsonConverter class, I would recommend using a JArray inside the converter instead of trying to deal with the reader directly. This will allow you to simplify your code while also avoiding the error you encountered:
class NullableBoolListDeSerializer : JsonConverter
{
readonly string[] TrueStrings = { "true", "yes", "1" };
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<bool?>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JArray.Load(reader)
.Children<JValue>()
.Select(jv =>
{
string b = (string)jv;
return b != null ? TrueStrings.Contains(b.ToLower()) : (bool?)null;
})
.ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Fiddle: https://dotnetfiddle.net/JaP5W7
Of course, you can actually do better than that. Instead of making your converter handle a List<bool?>, make it handle just a simple bool? instead, e.g.:
class NullableBoolDeSerializer : JsonConverter
{
readonly string[] TrueStrings = { "true", "yes", "1" };
public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string b = (string)reader.Value;
return b != null ? TrueStrings.Contains(b.ToLower()) : (bool?)null;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, swap out the [JsonConverter] attribute on your ExemptBenefits property declaration for a [JsonProperty] attribute that has its ItemConverterType parameter set to the simplified converter. Json.Net will then handle creating the list for you.
[JsonProperty(ItemConverterType = typeof(NullableBoolDeSerializer))]
public List<bool?> ExemptBenefits { get; set; }
Fiddle: https://dotnetfiddle.net/Dp4N11