2

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.

wingerse
  • 3,670
  • 1
  • 29
  • 61
  • If you are looking for a `JsonConverter` that can serialize an object as a string primitive, see `StringIdConverter` from [Json.Net: Serialize/Deserialize property as a value, not as an object](https://stackoverflow.com/a/40480742/3744182). – dbc Sep 03 '17 at 16:34
  • @dbc that's interesting. `JToken.Load(reader)` correctly reads the string. Do you know why reader.ReadAsString() doesn't? – wingerse Sep 03 '17 at 16:48
  • Yes, adding an answer now. – dbc Sep 03 '17 at 16:52

2 Answers2

10

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 };
    }
dbc
  • 104,963
  • 20
  • 228
  • 340
0

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\"}");
DvS
  • 1,025
  • 6
  • 11
  • Yep, this is the problem. I suspect `JsonReader` specifically looks for correctly defined JSON properties. – Daniel May Sep 03 '17 at 16:05
  • @DanielMay then why is JsonReader working fine when I use it manually? (As seen from the question) – wingerse Sep 03 '17 at 16:14
  • @Dvs I can't use that because I need to manually Deserialize. That's why I used a JsonConverter. – wingerse Sep 03 '17 at 16:16
  • @WingerSendon can you explain (1) why you need to 'manually' convert JSON (is a bit of a code smell) and (2) why are you trying to do this by inheriting from `JsonConverter`, which is I assume the Newtonsoft.Json type, which will expect valid JSON? – DvS Sep 03 '17 at 16:29
  • @Dvs I have a type which is a wrapper over a string (more specifically a "string enum" as seen from the accepted answer in [this](https://stackoverflow.com/questions/424366/c-sharp-string-enums) question). So it need to be converted into a Json string instead of a Json object. And this object isn't translated directly to json. It's inside another object so it might appear like this in Json: `{"MyStringEnum":"a value"}` (2) idk what you mean by that. Strings are converted to json strings. Arrays are converted to json arrays. I want my class to be converted to a json string. – wingerse Sep 03 '17 at 16:35