wingerse
wingerse

Reputation: 3796

Custom JsonConverter not working when using JsonReader instead of JsonSerializer

I have a class Foo and its FooConverter as defined below:

[JsonConverter(typeof(FooConverter))]
public class Foo
{
    public string Something { get; set; }
}

public class FooConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(((Foo)value).Something);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var str = reader.ReadAsString();
        if (str == null)
        {
            throw new JsonSerializationException();
        }    
        // return new Foo {Something = serializer.Deserialize<string>(reader)};
        return new Foo {Something = str};
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Foo);
    }
}

Serializing works fine. But when deserializing:

var foo = JsonConvert.DeserializeObject<Foo>("\"something\"");

it throws JsonSerializationException because reader.ReadAsString is null.
But I don't understand why it has to be null... reader.ReadAsString works perfectly find if I'm doing it manually like so:

var reader = new JsonTextReader(new StringReader("\"something\""));
var str = reader.ReadAsString(); // str is now `something` NOT null

Although I can fix FooConverter by using serializer.Deserialize<string>(reader) in ReadJson, I still want to understand why reader.ReadAsString fails in FooConverter.ReadJson.

Upvotes: 4

Views: 3838

Answers (2)

dbc
dbc

Reputation: 116585

Your problem is that, according to the documentation, JsonReader.ReadAsString():

Reads the next JSON token from the source as a String.

However, when JsonConverter.ReadJson() is called, the reader is already positioned on the first JSON token corresponding to the object being deserialized. Thus, by calling ReadAsString() you discard that value and try to read the next token in the stream -- but there is none, so you throw an exception.

Further, At the end of ReadJson() your code must have positioned the reader at the last JSON token corresponding to the object being converted. So, in the case where the JSON is simply a primitive, the reader should not get advanced at all.

A simple way to guarantee that the reader is always correctly positioned by ReadJson() is to call JToken.Load(). This always leaves the reader positioned at the end of the token that was loaded. Afterwards, you can check to see that what was loaded was as expected. E.g., if the JSON has an object where a string was expected, rather than leaving the reader incorrectly positioned, the converter should throw an exception rather than leave the reader incorrectly positioned.

StringIdConverter from Json.Net: Serialize/Deserialize property as a value, not as an object gives an example of this. You could modify it as follows:

public class FooConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var token = JToken.Load(reader);
        if (!(token is JValue))
            throw new JsonSerializationException("Token was not a primitive");
        return new Foo { Something = (string)token };
    }

Upvotes: 11

DvS
DvS

Reputation: 1095

Your "\"something\"" is not valid JSON, it's just an escaped string. JSON = JavaScript Object Notation, in which an empty object is defined as {}.

An object with state is just a dictionary structure, where keys have values. Your Foo type has a property Something, which is the key, and it needs to be assigned a value. So try a JSON object like this:

{ Something: "a value" }

which in C# would be converted like this:

var foo = JsonConvert.DeserializeObject<Foo>("{\"Something\":\"a value\"}");

Upvotes: 0

Related Questions