Reputation: 3796
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
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
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