6

I'm using Json.NET First look at this:

using System.Drawing;
string json = JsonConvert.SerializeObject(new Rectangle(-3,6,32,32), Formatting.Indented);
Console.WriteLine(json);
Rectangle deserializedRectangle = JsonConvert.DeserializeObject<Rectangle>(json);

Everything works as expected. The console output is: "3, 6, 32, 32"

But when I want to do the same thing with the XNA Rectangle, I get an error. (just replaced the old using with this "using Microsoft.Xna.Framework;")

The console output is: "{X:-3 Y:6 Width:32 Height:32}"

and the error it throws is: "Error converting value "{X:-3 Y:6 Width:32 Height:32}" to type 'Microsoft.Xna.Framework.Rectangle'."

  1. Why is this happening?

  2. Whats going wrong, and how do I fix this??

svick
  • 236,525
  • 50
  • 385
  • 514
Riki
  • 1,776
  • 1
  • 16
  • 31
  • Try building Json.NET from source so you can view the exception being thrown in the debugger. The answer to #1: it seems pretty clear that it's converting `Rectangle` using its `ToString` method, rather than extracting the individual member values, and there is no method to convert it back. I might guess that Json.NET uses public get/set properties (as `System.Drawing.Rectangle` has) and doesn't "see" `Microsoft.Xna.Framework.Rectangle`'s public *fields*. – Andrew Russell Jul 28 '11 at 09:57

3 Answers3

5

I've done some checking, this is the code that causes the exception:

    public static bool TryConvert(object initialValue, CultureInfo culture, Type targetType, out object convertedValue)
    {
      return MiscellaneousUtils.TryAction<object>(delegate { return Convert(initialValue, culture, targetType); }, out convertedValue);
    }

The actual call to the delegate that does the Convert work cannot find a convertor for this type. Investigating the cause for this, as the serializer is able to serialize and deserialize other types correctly.

EDIT:

This does not work, since the XNA Rectangle type is defined as:

    [Serializable]
    [TypeConverter(typeof(RectangleConverter))]
    public struct Rectangle : IEquatable<Rectangle>

Json.NET retrieves TypeConverter type, and calls this method on it:

  TypeConverter fromConverter = GetConverter(targetType);

  if (fromConverter != null && fromConverter.CanConvertFrom(initialType)) 
  {
       // deserialize
  }

The RectangleConverter has a flag saying "supportsStringConvert = false", so attempting to convert a string into it fails.

This is the reason that deserializing this specific object is failing.

lysergic-acid
  • 19,570
  • 21
  • 109
  • 218
  • Wow, awesome work! Thanks for finding this out. Do you have any idea how to resolve the problem, or how to work around it ? – Riki Jul 28 '11 at 23:20
  • You can try to serialize it into a different object (not string) if json.net allows it. I will check it out tomorrow – lysergic-acid Jul 29 '11 at 17:53
  • Looks like you'll need to create a simple DTO to serialize/deserialize the XNA Rectangle structure – MattDavey Mar 17 '12 at 15:39
2

I figured out a way to get Newtonsoft.Json (Json.Net) to play nice with XNA's Rectangle class. First, your rectangle should be a property of a class so you can give it a JsonConverter attribute:

public class Sprite
{
    [JsonConverter(typeof(MyRectangleConverter))]
    public Rectangle Rectangle;
}

public class MyRectangleConverter : JsonConverter
{
    public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
    {
        var rectangle = (Rectangle)value;

        var x = rectangle.X;
        var y = rectangle.Y;
        var width = rectangle.Width;
        var height = rectangle.Height;

        var o = JObject.FromObject( new { x, y, width, height } );

        o.WriteTo( writer );
    }

    public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    {
        var o = JObject.Load( reader );

        var x = GetTokenValue( o, "x" ) ?? 0;
        var y = GetTokenValue( o, "y" ) ?? 0;
        var width = GetTokenValue( o, "width" ) ?? 0;
        var height = GetTokenValue( o, "height" ) ?? 0;

        return new Rectangle( x, y, width, height );
    }

    public override bool CanConvert( Type objectType )
    {
        throw new NotImplementedException();
    }

    private static int? GetTokenValue( JObject o, string tokenName )
    {
        JToken t;
        return o.TryGetValue( tokenName, StringComparison.InvariantCultureIgnoreCase, out t ) ? (int)t : (int?)null;
    }
}

It could probably be improved so feedback is appreciated.

Big McLargeHuge
  • 14,841
  • 10
  • 80
  • 108
  • [How to write custom converters for JSON serialization](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to) – kot-da-vinci Jul 23 '20 at 00:16
0

This is by far the best solution I've found for this issue:

private class XnaFriendlyResolver : DefaultContractResolver {
  protected override JsonContract CreateContract(Type objectType) {
    // Add additional types here such as Vector2/3 etc.
    if (objectType == typeof(Rectangle)) {
      return CreateObjectContract(objectType);
    }

    return base.CreateContract(objectType);
  }
}

And just configure Newtonsoft.JSON to use the resolver

var settings = new JsonSerializerSettings() {
  ContractResolver = new XnaFriendlyResolver(),
};

var rect = JsonConvert.DeserializeObject<Rectangle>(jsonData, settings);
Elliott Darfink
  • 1,153
  • 14
  • 34