The following code will throw an exception:
class SimpleClassWithRegex
{
public Regex RegProp { get; set; }
}
[TestMethod]
public void RegexTest()
{
string json = JsonConvert.SerializeObject(new SimpleClassWithRegex {RegProp = null});
// json = {"RegProp":null}
SimpleClassWithRegex obj = JsonConvert.DeserializeObject<SimpleClassWithRegex>(json);
//Above line throws a JsonSerializationException
}
This seems like strange behavior to me, can someone explain why this isn't a bug? Or perhaps suggest a workaround?
Instantiating a Regex object in place of the null will of course stop this from throwing an exception.
The exception produced is:
Newtonsoft.Json.JsonSerializationException: Unexpected token when reading Regex. Path 'RegProp', line 1, position 15.
UPDATE
It appears that this issue was fixed in release 10.0.1 (March 2017).
This looks like a bug in Json.Net's RegexConverter class. The ReadJson method looks like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
return ReadRegexObject(reader, serializer);
}
if (reader.TokenType == JsonToken.String)
{
return ReadRegexString(reader);
}
throw JsonSerializationException.Create(reader, "Unexpected token when reading Regex.");
}
As you can see, it is not expecting or checking for a null token from the reader, so it is falling through to the line which throws a JsonSerializationException.
You may want to report an issue linking back to this SO question.
As a workaround, you can derive your own converter from the RegexConverter and add the check:
public class ImprovedRegexConverter : RegexConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
When you deserialize, pass an instance of your custom converter to the DeserializeObject method like this:
SimpleClassWithRegex obj =
JsonConvert.DeserializeObject<SimpleClassWithRegex>(json, new ImprovedRegexConverter());
Alterntatively, you can add a [JsonConverter] attribute to your Regex class members like this:
class SimpleClassWithRegex
{
[JsonConverter(typeof(ImprovedRegexConverter))]
public Regex RegProp { get; set; }
}
Fiddle: https://dotnetfiddle.net/BIqmd6
Related
I'm having some difficulties in deserializing a Boolean from a json. I need that the value can be case insensitive (faLSe, tRUE, etc) and if it's an invalid value (ex: qwerty) I'll return a null. I've created a custom converter:
public class NullableBooleanJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = reader?.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (bool.TryParse(value, out bool deserializedValue))
{
return deserializedValue;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
And I'm using the code like this:
JsonSerializerSettings setting = new JsonSerializerSettings
{ Converters = new List<JsonConverter> { this.converter } };
JsonConvert.DeserializeObject<bool?>("false", setting).Should().BeFalse();
and this works, but if I have on the last line:
JsonConvert.DeserializeObject<bool?>("faLSE", setting).Should().BeFalse();
The test fails with the message:
TestName threw exception:
Newtonsoft.Json.JsonReaderException: Error parsing boolean value. Path '', line 1, position 2
What I'm doing wrong?
If you look at the source code of DeserializeObject it uses the JsonTextReader class to parse the Json. If you look at the source code of JsonTextReader it parses only "true" (if first char is a lowercase t) or "false" (if first char a lowercase f) strings (row 1720), throwing an exception in any other case.
The simplest way to realize what you need is to download the full Newtonsoft.Json source code, add a String.ToLower to ParseTrue and ParseFalse methods in JsonTextReader.cs, recompile and use the new dll in your project.
I have a generic type Container<IContentType> where interface IContentType can be one of four concrete ContentX types.
I serialize and everything is fine.
When deserializing using Newtonsoft I use custom type converters and var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings) works. The debugger shows I have an Container<ContentA> object.
My plan was when deserializing to attempt a deserialization for each of the four possible ContentX types and catch an exception silently until I "guess" the right one.
However, if I do this within a method like so:
public static Container<IContentType> Deserialize(jsonfile)
{
...
var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings)
return model;
}
I get "Cannot implicitly convert Container<ContentA> to Container<IContentType>". ContentA implements IContentType.
Is there a way I can create a cast operator, conversion, dynamic or make the implicit conversion work?
Rather than trying to deserialize as a Container<ContentX> for concrete type(s) X, you should deserialize as a Container<IContentType> using a custom JsonConverter that pre-load the JSON into a JToken and infers the concrete type along the lines of How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net or Json.Net Serialization of Type with Polymorphic Child Object.
Thus your converter would look something like:
public class ContentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IContentType);
}
Type GetConcreteType(JObject obj)
{
if (obj.GetValue(nameof(ContentA.SomePropertyOfContentA), StringComparison.OrdinalIgnoreCase) != null)
return typeof(ContentA);
// Add other tests for other content types.
// Return a default type or throw an exception if a unique type cannot be found.
throw new JsonSerializationException("Cannot determine concrete type for IContentType");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var concreteType = GetConcreteType(obj);
return obj.ToObject(concreteType, serializer);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And your call to JsonConvert would look like:
var settings = new JsonSerializerSettings
{
Converters = { new ContentConverter() },
};
var model = JsonConvert.DeserializeObject<Container<IContentType>>(json, settings);
Finally, you might be able to choose the type entirely automatically using
new JsonDerivedTypeConverer<IContentType>(typeof(ContentA), typeof(ContentB), typeof(ContentC), typeof(ContentD))
Where JsonDerivedTypeConverer<T> is taken from JsonConverter with Interface.
I'd like to have property of type object that can be either a string or Template type.
Is it possible to tell Json.NET to parse something into one of several specified types?
class MyClass
{
public object Template { get; set; }
}
where Template = "TemplateName"
{
"Template": "TemplateName"
}
or Template = new Template()
{
"Template": { "Name": "OtherTamplate", ... }
}
UPDATE:
I tried to follow #krillgar' advice and create a custom JsonConverter but unfortunatelly the CanConvert method receives only the target type, in this case object. This information is not enough to tell wheter it can be deserialized (in case I had other object properties). I guess I need it to be a Template after all or create a derived type like TemplateReference or something:
class myconverter : 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)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// objectType = typeof(object)
throw new NotImplementedException();
}
}
Configuration = JsonConvert.DeserializeObject<MyClass>(text, new myconverter());
Disclaimer
This question has once been closed as a duplicate of How to deserialize a JSON property that can be two different data types using Json.NET. Because at the time of writing my question I hadn't known that there already was a similar one I'd like to clarify the difference between them to prevent it from being closed in future:
The other question is about how to deserialize different values into a concrete type whereas mine is about deserializing different values into an object. It might seem to be the same at the first look because in both examples only the type of the property is different but it has a huge impact on the overall application design. It's important for me that I can use an object to store different specialized types rather then one type having multiple responsibilities.
This problem can be solved by using a custom JsonConverter. Here is a generic version that should work for this situation:
class ObjectOrStringConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when the [JsonConverter] attribute is used
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject<T>(serializer);
}
return token.ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
To use the converter, all you need to do is add a [JsonConverter] attribute to the property in your class that can be either a string or an object. The generic type parameter must match the type of non-string object you are expecting.
class MyClass
{
[JsonConverter(typeof(ObjectOrStringConverter<Template>))]
public object Template { get; set; }
}
Below is a demonstration of the converter in action:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("--- first run ---");
string json = #"
{
""Template"": ""TemplateName""
}";
DeserializeAndDump(json);
Console.WriteLine("--- second run ---");
json = #"
{
""Template"": { ""Name"": ""OtherTemplate"" }
}";
DeserializeAndDump(json);
}
static void DeserializeAndDump(string json)
{
MyClass obj = JsonConvert.DeserializeObject<MyClass>(json);
if (obj.Template == null)
{
Console.WriteLine("Template property is null");
}
else
{
Console.WriteLine("Template property is a " + obj.Template.GetType().Name);
string name = "(unknown)";
if (obj.Template is Template) name = ((Template)obj.Template).Name;
else if (obj.Template is string) name = (string)obj.Template;
Console.WriteLine("Template name is \"" + name + "\"");
}
Console.WriteLine();
}
}
class Template
{
public string Name { get; set; }
}
And here is the output from the above:
--- first run ---
Template property is a String
Template name is "TemplateName"
--- second run ---
Template property is a Template
Template name is "OtherTemplate"
Fiddle: https://dotnetfiddle.net/Lw3RaN
I don't know if you can do that, but you could go another way. Change your "Template" property to be a Template instead of an object and use a custom property of the Template class to know weither you want to serialize it as a Template or a string.
class MyClass
{
[JsonConverter(typeof(TemplateConverter))]
public Template Template { get; set; }
}
class Template
{
/* Your Template class */
public Type TypeToSerializeInto { get; private set; }
}
public class TemplateConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Template);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Template val = value as Template;
writer.WriteStartObject();
writer.WritePropertyName("Template");
if (val.TypeToSerializeInto == typeof(Template))
serializer.Serialize(writer, val);
else if (val.TypeToSerializeInto == typeof(string))
serializer.Serialize(writer, val.Name);
writer.WriteEndObject();
}
}
If you have a property, which type is an abstract type - like object, on de-serialization, you can know the specific type that was serialized by serializing somewhere also the name of that specific type.
So your json should look like this:
{
"MyClass": {
"Template": "some name",
"type": "System.String"
}
}
This way on deserialization you can check what type was that property before the serialization (in this case String)
Another way to determine the type is by checking the json structure as you can see here:
C#: Deserializing JSON when one field can be different types
I have written a custom JsonConverter which I am hoping will allow me to serialize and deserialize Encoding objects within my classes:
public class EncodingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsSubclassOf(typeof(Encoding));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Encoding)value).EncodingName);
}
public override bool CanRead { get { return true; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var name = reader.ReadAsString();
return Encoding.GetEncoding(name);
}
}
However, when I run the following test code, I get an exception when calling DeserializeObject, and the ReadJson method never gets called.
class Program
{
private static void Main(string[] args)
{
var test = new TestClass();
var jsonSettings = new JsonSerializerSettings
{
Converters = new[] { new EncodingConverter(), }
};
var json = JsonConvert.SerializeObject(test, jsonSettings);
var test2 = JsonConvert.DeserializeObject<TestClass>(json, jsonSettings);
}
}
class TestClass
{
public string Property1;
public Encoding Encoding = Encoding.UTF8;
}
The exception message is:
Target type System.Text.Encoding is not a value type or a non-abstract class.
Am I missing something?
There are three problems with your converter that I see.
You are using the wrong check in CanConvert().
You are using the wrong name for the Encoding when serializing.
You are using the wrong method to get the value from the reader when deserializing.
Let's take these one at a time.
First, in your CanConvert method you are using objectType.IsSubclassOf(typeof(Encoding)) to determine whether the converter should handle the Encoding. This works fine on serialization because you have a concrete instance of the encoding (e.g. UTF8Encoding), which is indeed a subclass of Encoding. However, on deserialization, the deserializer doesn't know what concrete type of encoding you are going to make, so the type that is passed to the converter is just Encoding. Since Encoding is not a subclass of itself, CanConvert returns false, and your ReadJson method never gets called. That leaves Json.Net to try to instantiate the Encoding itself, which it can't do (because Encoding is abstract), so it throws the error you mentioned in your question. You should instead use typeof(Encoding).IsAssignableFrom(objectType) inside your CanConvert method.
Second, when serializing the Encoding inside WriteJson, you are outputting the EncodingName property, which is the human-readable display name of the encoding, not the code page name. If you look at the documentation for the Encoding.GetEncoding(string) method, it says:
Parameters
name
Type: System.String
The code page name of the preferred encoding. Any value returned by the WebName property is valid. Possible values are listed in the Name column of the table that appears in the Encoding class topic.
So, you should be outputting the value of the WebName property in your WriteJson method if you want to be able to use this value to later reconstruct the Encoding in ReadJson.
Third, in your ReadJson method you are using reader.ReadAsString() to attempt to get the encoding name from the JSON. This will not work as you expect. When ReadJson is called by Json.Net, the reader is already positioned at the current value. When you call ReadAsString(), that advances the reader to the next token and then attempts to interpret that token as a string. What you really want to do is just get the value of the current token, which you can do using the Value property. Because Value is of type object, you will need to cast it to a string.
Here is the corrected code for the converter:
public class EncodingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Encoding).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Encoding)value).WebName);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Encoding.GetEncoding((string)reader.Value);
}
}
Fiddle: https://dotnetfiddle.net/UmLynX
Try:
public class CustomConverter : JsonConverter
{
public override bool CanConvert(System.Type objectType)
{
return true;// objectType.IsAssignableFrom(typeof(Encoding));
}
public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
{
return Encoding.GetEncoding(Convert.ToString(reader.Value));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var t = (Test)value;
var e = (Encoding)t.MyProperty;
writer.WriteValue(e.BodyName);
//serializer.Serialize(writer, e.BodyName);
}
}
And in Main:
var o = new Test { MyProperty = Encoding.UTF8 };
var s = new JsonSerializerSettings
{
Converters = new[] { new CustomConverter() }
};
var v = JsonConvert.SerializeObject(o, s);
var o2 = new Test();
o2.MyProperty = Encoding.GetEncoding(JsonConvert.DeserializeObject(v, s).ToString());
I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:
The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.
I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.
In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.
[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }
The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.
If it's already an array, it should just leave it alone.
The converter looks like this:
public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(List<T>))
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
var singleObject = serializer.Deserialize<T>(reader);
var objectAsList = new List<T>();
objectAsList.Add(singleObject);
return objectAsList;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).
I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.
I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.
I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.
I'm a bit out of my depth on this one.
I had to do something similar last week and I came up with the following, which works fine for a List rather than an array
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).
public class FlexibleCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
var array = new JArray(JToken.ReadFrom(reader));
return array.ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return typeof (IEnumerable).IsAssignableFrom(objectType);
}
}