Creating a custom JsonConverter to handle System.Text.Encoding objects
I wrote a custom JsonConverter that I hope will allow me to serialize and deserialize Encoding
objects in 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 called DeserializeObject
, and the method is ReadJson
never 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;
}
Exception message:
The System.Text.Encoding target type is not a value type or an abstract class.
Did I miss something?
source to share
There are three problems I see with your converter.
- You are using the wrong check in
CanConvert()
. - You are using the wrong name for the
Encoding
. - You are using the wrong method to get the value from the reader when deserializing.
Let them take this data at a time.
First, in your method, CanConvert
you use objectType.IsSubclassOf(typeof(Encoding))
to determine if the converter should handle Encoding
. This works great on serialization because you have a specific encoding instance (for example UTF8Encoding
) that is indeed a subclass Encoding
. However, when deserializing, the deserializer does not know what specific type of encoding you are going to do, so the type passed to the converter is equal to everything Encoding
. Since Encoding
it is not a subclass of itself, it CanConvert
returns false and your method is ReadJson
never called. This leaves Json.Net trying to instantiate itself Encoding
, which it cannot do (sinceEncoding
is abstract), so it throws the error mentioned in your question. Use typeof(Encoding).IsAssignableFrom(objectType)
inside your method instead CanConvert
.
Second, when serializing Encoding
internally, WriteJson
you output a property EncodingName
that is the encoding display name on screen, not the code page name. If you look at the documentation for the method Encoding.GetEncoding(string)
, it says:
Parameters
name
Type: System.String
Preferred encoding code page name. 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 section.
So, you must output the property value WebName
to your method WriteJson
if you want to use that value to later restore Encoding
to ReadJson
.
Third, in your method ReadJson
you are using reader.ReadAsString()
to try and get the encoding name from the JSON. This will not work the way you expect. When ReadJson
Json.Net is called, the reader is already positioned with the current value. When you call ReadAsString()
, it advances the reader to the next token and then tries 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 property Value
. Since it Value
has a type object
, you will need to pass 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
source to share
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());
source to share