4

I have a file with some json in it, that was generated by json.net:

[
  {
    "$type": "Dashboard.Gauges.LabelGaugeSeed, Dashboard",
    "Text": "blah",
    "LabelColor": {
      "X": 1.0,
      "Y": 1.0,
      "Z": 1.0,
      "W": 1.0
    },
    "Center": {
      "X": 0.0,
      "Y": 0.0
    },
    "CharacterWidth": 0.05,
    "CharacterHeight": 0.1,
    "LineThickness": 0.01,
    "TextCentering": 0.5
  }
]

Which is giving me the aforementioned error while deserializing. Can anyone find a problem with this json? I ran it through a verifier and it said it was fine.

It errors on the space after "Center": If I change the order of the Center and the LabelColor properties, it then errors in the same manner after "LabelColor":

Here is a dump of the types:

LabelColor is an OpenTK Vector4, Center is an OpenTK Vector2, and LabelGaugeSeed is below:

public class LabelGaugeSeed : IGaugeSeed
{
    public IGauge Grow()
    {
        return new LabelGauge(this);
    }

    public string Text;
    [JsonConverter(typeof(Vector4Converter))]
    public Vector4 LabelColor;
    [JsonConverter(typeof(Vector2Converter))]
    public Vector2 Center;
    public float CharacterWidth;
    public float CharacterHeight;
    public float LineThickness;
    public float TextCentering;
}

And here is Vector4Converter:

public class Vector4Converter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Vector2);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        reader.Read();
        reader.Read();
        var x = serializer.Deserialize<float>(reader);

        reader.Read();
        reader.Read();
        var y = serializer.Deserialize<float>(reader);

        reader.Read();
        reader.Read();
        var z = serializer.Deserialize<float>(reader);

        reader.Read();
        reader.Read();
        var w = serializer.Deserialize<float>(reader);

        return new Vector4(x, y, z, w);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Vector4 vectorValue = (Vector4)value;

        writer.WriteStartObject();

        writer.WritePropertyName("X");
        writer.WriteValue(vectorValue.X);

        writer.WritePropertyName("Y");
        writer.WriteValue(vectorValue.Y);

        writer.WritePropertyName("Z");
        writer.WriteValue(vectorValue.X);

        writer.WritePropertyName("W");
        writer.WriteValue(vectorValue.Y);

        writer.WriteEndObject();
    }
}

Vector2Converter is exactly the same, except for it doesn't contain code for the Z and W properties, and the name is different.

The deserialization process will go through the first one successfully, but not even get into the second one.

The Vector classes can be found here: https://github.com/opentk/opentk/tree/develop/Source/OpenTK/Math

13
  • Can you also post the C# types? Commented Jan 18, 2015 at 19:44
  • 1
    @UfukHacıoğulları I have posted them. Commented Jan 18, 2015 at 19:54
  • 1
    @DanielA.White The error message is as follows: An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll Additional information: Unexpected token while deserializing object: PropertyName. Path '[0].Center', line 11, position 14. Which is the space after "Center": Commented Jan 18, 2015 at 20:03
  • 1
    Why do you even need a converter? X, Y, Z and W are public, writable fields so they serialize correctly out of the box. Commented Jan 18, 2015 at 20:09
  • 1
    @dbc because the vector* structs in opentk have quite a few fields that are not just x, y, z and w and they result in recursive references (for instance public Vector2 PerpendicularRight). Plus just having x, y, z and w makes the json way more readable. Commented Jan 18, 2015 at 20:11

2 Answers 2

14

Tl;dr - The problem was in your Vector[X]Converters. You read all the properties, but didn't actually navigate to the end of the object. You need an extra reader.Read() right before the last line where you return your concrete object.

The deeper explanation:

JSON.NET is picky about the state of the JsonReader when you are writing a custom converter. You need to traverse all the way to the end of the reader, regardless of whether you actually need the remaining data (in other words, you can't return early).

In this example, you read the value you wanted (W), and then immediately returned a new concrete object since you have all the data you need. However, the JsonReader is still examining the property node, so JSON.NET thinks that there's still data waiting to be deserialized. This is why you get

Additional text found in json string after finishing deserializing object

You can see this yourself if you put a breakpoint inside your JsonConverter and watch the state of the reader object as you advance through the tokens. On the last one, the state is:

...
Path: "LabelColor.W"
TokenType: Float
Value: 1.0
....

If you leave the JsonReader in that state, you'll get an error. But, if you do one last reader.Read(), then the state is:

...
Path: "LabelColor"
TokenType: EndObject
Value: null
...

Now JSON.NET is happy!

Sign up to request clarification or add additional context in comments.

4 Comments

Because of weirdness like this, I would recommend using a JToken-based reader, like dbc posted.
Thank you for the explanation to the problem. I have actually used dbc's answer, so I will mark that one as accepted. Plus it should encourage people to create these correctly. The JToken solution is much more elegant than what I had.
It is more elegant for sure. Hopefully my explanation may help someone else who is trying to figure out why JSON.NET is barfing. Cheers!
This answer has helped me identify an issue within my JsonConverter. My while loop stopping condition was While(reader.Read()) which was not enough since I had other objects that I was not done deserializing. I had to change my while loop to check for reader.TokenType. In my case, I was checking for JsonToken.EndArray.
9

The easiest way to do the read is to load into a JToken and access the properties by name, like so:

public class Vector4Converter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Vector4);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        var vec = new Vector4();

        if (token["X"] != null)
            vec.X = (float)token["X"];
        if (token["Y"] != null)
            vec.Y = (float)token["Y"];
        if (token["Z"] != null)
            vec.Z = (float)token["Z"];
        if (token["W"] != null)
            vec.W = (float)token["W"];
        return vec;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Vector4 vectorValue = (Vector4)value;

        writer.WriteStartObject();

        writer.WritePropertyName("X");
        writer.WriteValue(vectorValue.X);

        writer.WritePropertyName("Y");
        writer.WriteValue(vectorValue.Y);

        writer.WritePropertyName("Z");
        writer.WriteValue(vectorValue.Z);

        writer.WritePropertyName("W");
        writer.WriteValue(vectorValue.W);

        writer.WriteEndObject();
    }
}

Loading into a JToken and accessing by name also allows the sender to write the JSON properties in any order, which is preferred since the JSON spec states that property name/value pairs are unordered.

Also, notice I fixed a couple mistakes in WriteJson (the code wrote X and Y twice) and a mistake in CanConvert.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.