JsonConverter CanConvert does not receive type - c#

I have a custom JsonConverter, which doesn't seem to be called correctly. I have created the converter, added it to the JsonSerializerSettings.Converters collection and marked the property on the entity I am serialising with [JsonConverter(typeof(SearchGeoConverter))], but even with these in place the converters CanConvert method never sees the type I am trying to convert. I only ever see, string, int and JObject.
My converter looks like this:
public class SearchGeoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DbGeography).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var point = (DbGeography) value;
var rawJson = string.Format("{{ \"type\": \"Point\", \"coordinates\": [{0}, {1}] }}", point.Latitude, point.Longitude);
writer.WriteRaw(rawJson);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What am I missing?

CanConvert does not get called when you mark something with [JsonConverter]. When you use the attribute, Json.Net assumes you have provided the correct converter, so it doesn't bother with the CanConvert check. If you remove the attribute, then it will get called by virtue of you passing the converter instance to the settings. What you are seeing is Json.Net testing your converter for all the other property types.
EDIT
I put together a quick fiddle to show what I mean (code is also reproduced below for completeness).
With no changes the program, CanConvert() gets called on the FooConverter for all types except Foo, yet it still converts Foo correctly.
If you comment out the [JsonConverter] attribute on the Wrapper.Foo property, you can see that CanConvert() will now get called for type Foo by virtue of the FooConverter being included in the JsonSerializerSettings.
If you instead comment out the line in Main where the FooConverter is added to the settings, then CanConvert is never called for any type, yet Foo is still converted correctly due to the [JsonConverter] attribute applied to the Foo property in the Wrapper class.
So the takeaway here is that there are two mechanisms for indicating whether a converter should be used, and you don't need both. You can apply an attribute, and that will tell Json.Net that a particular converter should be used for a particular property (or class) and it does not need to ask the converter first. Alternatively, you can add the converter to the settings, in which case Json.Net has to ask each converter whether it can handle each type. The former is a bit more efficient, while the latter is useful in situations where you don't own the source code for the class you're trying to convert. Hope this makes sense.
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
public class Program
{
public static void Main()
{
JsonSerializerSettings settings = new JsonSerializerSettings();
// Comment out the following line and CanConvert() never gets called on
// FooConverter for any type yet the FooConverter is still working due
// to the JsonConverter attribute applied to Wrapper.Foo
settings.Converters.Add(new FooConverter());
settings.Converters.Add(new BarConverter());
settings.Formatting = Formatting.Indented;
Wrapper w = new Wrapper
{
Foo = new Foo
{
A = "bada",
B = "boom",
},
Bar = new Bar
{
C = "bada",
D = "bing"
}
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
class Wrapper
{
// Comment out this attribute and CanConvert will be called on FooConverter
// for type Foo due to the fact that the FooConverter has been added to the
// JsonSerializerSettings
[JsonConverter(typeof(FooConverter))]
public Foo Foo { get; set; }
public Bar Bar { get; set; }
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
}
class Bar
{
public string C { get; set; }
public string D { get; set; }
}
class FooConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Foo).IsAssignableFrom(objectType);
Console.WriteLine("FooConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var foo = (Foo) value;
JObject jo = new JObject();
jo.Add("AplusB", new JValue(foo.A + " " + foo.B));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class BarConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Bar).IsAssignableFrom(objectType);
Console.WriteLine("BarConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bar = (Bar) value;
JObject jo = new JObject();
jo.Add("CplusD", new JValue(bar.C + " " + bar.D));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

Related

Unexpected token: StartObject, when deserializing OneOf union type using custom converter

I am trying to deserialize the following JSON (which validates on https://jsonlint.com/):
{"pandoc-api-version":[1,22],"meta":{"title":{"t":"MetaBlocks","c":[{"t":"Para","c":[{"t":"Str","c":"Dynamic"},{"t":"Space"},{"t":"Str","c":"Language"},{"t":"Space"},{"t":"Str","c":"Runtime"}]},{"t":"Para","c":[]}]}},"blocks":[{"t":"Para","c":[{"t":"Strong","c":[{"t":"Str","c":"Bill"},{"t":"Space"},{"t":"Str","c":"Chiles"},{"t":"Space"},{"t":"Str","c":"and"},{"t":"Space"},{"t":"Str","c":"Alex"},{"t":"Space"},{"t":"Str","c":"Turner"}]}]},{"t":"Para","c":[{"t":"Emph","c":[{"t":"Strong","c":[{"t":"Str","c":"Reading"},{"t":"Space"},{"t":"Str","c":"this"},{"t":"Space"},{"t":"Str","c":"Document:"}]}]}]}]}
into the following classes:
internal record TagContent(string T, OneOf<TagContent[], string>? C);
internal class RawPandoc {
[JsonProperty] public int[] PandocApiVersion = default!;
[JsonProperty] public Dictionary<string, TagContent> Meta = default!;
[JsonProperty] public TagContent[] Blocks = default!;
}
using the following code:
var settings = new JsonSerializerSettings {
ContractResolver = new DefaultContractResolver { NamingStrategy = new KebabCaseNamingStrategy() },
Converters = new JsonConverter[] { new OneOfJsonConverter() }
};
var pandoc = JsonConvert.DeserializeObject<RawPandoc>(s, settings);
and I get the following error:
Unexpected token when deserializing object: StartObject. Path 'meta.title.c[0]', line 1, position 69.
How can I resolve this?
For completeness, here is the current and incomplete code for OneOfJsonConverter. OneOf is a library for union types in C#:
using OneOf;
namespace PandocFilters {
public class OneOfJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
if (value is IOneOf of) {
value = of.Value;
}
serializer.Serialize(writer, value);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
if (reader.Value is null) { return null; }
// TODO not implemented yet
return reader.Value;
}
public override bool CanConvert(Type objectType) => objectType.UnderlyingIfNullable().GetInterfaces().Contains(typeof(IOneOf));
}
}
Problem is you are not advancing the reader in your ReadJson implementation. You declared your converter can handle IOneOf objects, and so JSON.NET expects your converter to actually read and handle it, however it does nothing as of now. So ReadJson is called (at the start of first array in json which should be deserialized to OneOf), and then after it returns - reader position is still where it was before (at start of array), which is not what JSON.NET expects. Then it fails trying to continue reading next object, because its assumptions are violated. So, just implement ReadJson, and meanwhile you can advance a reader for example like that:
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
// advance reader as expected
var eitherStringOrArray = JObject.ReadFrom(reader);
return reader.Value;
}

How can I customize the deserialization of a nested property in Json.net without adding attributes

Suppose I have these classes:
public class Bar
{
public Foo MyFoo { get; set; }
}
public class Foo
{
public string[] Stuff { get; set; }
}
And I have this JSON structure:
{
"MyFoo":
{
"Stuff":"those,are,my,stuff"
}
}
And I have a code path where a JObject is being converted to Bar using code like below:
myJObject.ToObject(typeof(Bar))
Now what I need to do is to supply the ToObject with a custom serializer to convert the string property Stuff into an array of string (using string.Split(...).ToArray())
I was asked not to add attributes on the client class 'Bar' so after looking around it seemed like a ContractResolver is in order but the issue is that the resolver only lets me handle direct properties of the root Type, that is Bar in my example, and I can't register a JsonConverter on a nested property.
So my question to you guys is, is this even achievable using Json.net?
Note that I need to do this not only for the Bar class but to an unlimited amount of classes with unknown structure so I can't hard-code a solution that will work for one type of class.
Based on your description, I don't see why you would need a ContractResolver. You know that the properties you want to deserialize specially will always be of type string[], so just make a converter that handles that type. Maybe something like this:
public class CsvStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
return null;
if (token.Type == JTokenType.String)
return ((string)token).Split(',');
if (token.Type == JTokenType.Array)
return token.ToObject<string[]>(serializer);
throw new JsonException("Unexpected token type: " + token.Type);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to use it with your JObject, create a new JsonSerializer instance, add the converter to it, and pass the serializer to the ToObject() method like this:
var serializer = new JsonSerializer();
serializer.Converters.Add(new CsvStringConverter());
var bar = myJObject.ToObject<Bar>(serializer);
Working demo here: https://dotnetfiddle.net/qmeBoh

Newtonsoft.Json v11 - DeserializeObject throws JsonReaderException

We have upgraded Newtonsoft.Json from 10.0.3 to 11.0.1 and code that has been working previously does not work anymore.
We have following JSON:
[{"mail-type":"registration","response":"250 OK id=1UjnNr-gdf-C0 ","invoice-id":,"email":"testuser08#test.com"}]
and we call following method:
var events = JsonConvert.DeserializeObject<Event[]>(jsonEvents);
This worked fine on 10.0.3, but does not on 11.0.1. On this version following exception is thrown:
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in
Newtonsoft.Json.dll
Additional information: An undefined token is not a valid
System.Nullable`1[System.Int64]. Path '[0].invoice-id', line 1.
I tried to pass following options to DeserializeObject
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
But still the same error. What changes have to be done to make it work on 11.0.1. I am affraid we cannot accomodate JSON output as this comes from third party API.
Your JSON sample is not well-formed. If I upload your JSON to https://jsonlint.com/ then the following error is generated:
Error: Parse error on line 4:
...0 ", "invoice-id": , "email": "testuse
----------------------^
Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '[', got ','
The line in question is as follows:
"invoice-id": ,
According to the JSON standard there needs to be a value between the : and the ,. But, why did this work in Json.NET 10.0? Apparently it was a bug, which was fixed. According to the 11.0.1 release notes:
Fix - Fixed not erroring when reading undefined for nullable long
So if we assume that your Event type looks like this:
public partial class Event
{
[JsonProperty("invoice-id")]
public long? InvoiceId { get; set; }
// Other properties as required
}
Then in 10.0 your JSON could be deserialized successfully using this type, but in 11.0 it cannot. If, however, we change InvoiceId to be an int?:
public partial class Event
{
[JsonProperty("invoice-id")]
public int? InvoiceId { get; set; }
// Other properties as required
}
It fails in both versions. Thus the fix appears to have been to handle int? and long? consistently.
Ideally, you should ask whoever sent you such JSON to fix it so that it is well-formed as defined by http://www.json.org/ and RFC 8259. If you nevertheless need to parse such JSON in the same manner as Json.NET 10.0, you could introduce TolerantNullableLongConverter as follows:
public class TolerantNullableLongConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType == JsonToken.Undefined)
return null;
if (reader.Value is long)
return reader.Value;
// string or int or decimal or ...
return serializer.Deserialize<long>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And apply it to your type as follows:
public partial class Event
{
[JsonProperty("invoice-id")]
[JsonConverter(typeof(TolerantNullableLongConverter))]
public long? InvoiceId { get; set; }
// Other properties as required
}
You can implement workaround with custom converter:
internal class NullableLongFixupConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// if token undefined - return null
if (reader.TokenType == JsonToken.Undefined)
return null;
// otherwise - value
return (long?) reader.Value;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(long?);
}
}
Then you can either decorate properties with it:
class Event {
[JsonProperty("invoice-id")]
[JsonConverter(typeof(NullableLongFixupConverter))]
public long? InvoiceId { get; set; }
}
or register globally (will be invoked only for properties of type long?):
JsonConvert.DefaultSettings = () =>
{
var s = new JsonSerializerSettings();
s.Converters.Add(new NullableLongFixupConverter());
return s;
};

How to serialize a Guid with C# without creating an object?

I want my Guids to be serialized into a short form of Guid (as seen here). For full compatibility, I want the ShortGuid class to serialize into the short form, and not the long form.
I've tried using custom serialization, but I can't seem to make the whole object serialize into the short string, only into an object which contains the string. This is what I've tried:
[Serializable]
public class ShortGuid : ISerializable
{
public Guid Guid { get; }
protected ShortGuid(SerializationInfo info, StreamingContext context)
{
Guid = Decode(info.GetString("Guid"));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Guid", Encode(Guid));
}
}
Which works, but if I serialize it:
var guid = new ShortGuid(Guid.NewGuid());
var str = JsonConvert.Serialize(guid);
The serialized string I get looks like this:
{ Guid: "xxxxxxxxxxxxxxxxxx" }
While the serialized string I WANT is just
xxxxxxxxxxxxxxxxxx
I've turned everything around, but can't get this to work. How can this be done?
NOTE: I don't want this to work only on JsonConvert, this is only an example. I would like the class the always be serialized correctly.
this work perfectly for me:
public class GuidShortGuidConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guid);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var shortGuid = new ShortGuid(reader.Value.ToString());
return shortGuid.Guid;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var customValue = new ShortGuid((Guid) value);
writer.WriteValue(customValue.ToString());
}
}
What you want is not about serialization but implicit casting.
Serialization works on structured key/value base depending on serialization type.
With implicit string casting
public static implicit operator string(ShortGuid shortGuid)
{
return Encode(sh.Guid);
}
You can use
var guid = new ShortGuid(Guid.NewGuid());
string str = guid;

Convert specific objects to dictionaries before serialization

I'm using Json.NET to serialize validation data for data field. On the .NET side, validation data is a list of ValidationAttribute objects. However, I'd like to serialize them in a special form like this:
[
{ Type: 'Required', ErrorMessage: '{FieldName} is required' },
{ Type: 'RegularExpression', Pattern: '^\d+$', ErrorMessage: '...'
]
In an ideal solution I could simply intercept the object before serialization and, I could create a corresponding Dictionary<string, object> object to serialize instead of the original one.
Are there any solutions for this scenario?
You can implement your own JsonConverter class and convert your collection as you wish.
You just need to create you class and inherit it from JsonConverter
public class YourSerializer : 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)
{
return typeof(YourClassName).IsAssignableFrom(objectType);
}
}
and then you need to decorate your class which will be serialized with the attribute (looks like it's not what you want)
[JsonConverter(typeof(YourSerializer))]
public class YourClassName
{
public string Name { get; set; }
public string Value { get; set; }
}
or, pass an instance of your serializer to Serialize methos:
string json = JsonConvert.SerializeObject(sourceObj, Formatting.Indented, new YourSerializer(typeof(yourClassName)));
Here is a few links:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/
Hope, it will help.

Categories