BSON bug?

Jan 11, 2010 at 7:53 PM

Hi there.

 

I'm currently using the BSON stuff (json35r6) and i think i've found a bug.

If I serialize and deserialize a RemoteFunction object (see below) that contains a List<> or Dictionary<> that has no items in it (but is not null!),

the BsonReader will throw this exception :

throw new ArgumentOutOfRangeException("type", "Unexpected BsonType value: " + type);

(line436, ReadType() method)

 

Any help would be appreciated

and keep up the amazing work!

 

bye for now,

Hans

 

 

Here's my code:

 

 

the object I'm serializing:

 

 

   public class RemoteFunction
   {
      public string Function = "";
      public Dictionary<string, object> Arguments = new Dictionary<string, object>();
     
      //tried a list<> to see if that works, but same result
      public List<string> testlist = new List<string>();
      public string ReturnValue;

      public RemoteFunction()
      {
        // testlist.Add("haha");
      }
   }
My BSON code/wrapper:
   public class BsonSerializer<TObj>
   {
      public byte[] Serialize(TObj o)
      {
         MemoryStream _Stream = new MemoryStream();
         Newtonsoft.Json.Bson.BsonWriter _BsonWriter = new Newtonsoft.Json.Bson.BsonWriter(_Stream);
         JsonSerializer _JsonSerializer = new JsonSerializer();
         _JsonSerializer.Serialize(_BsonWriter, o);
         _Stream.Close();
         _BsonWriter.Close();
         return _Stream.ToArray();
      }

      public TObj Deserialize(byte[] b)
      {
         JsonSerializer _JsonSerializer = new JsonSerializer();
         //_JsonSerializer.MissingMemberHandling = MissingMemberHandling.Ignore;
         Newtonsoft.Json.Bson.BsonReader _BsonReader = new Newtonsoft.Json.Bson.BsonReader(new MemoryStream(b));
         TObj _Return = _JsonSerializer.Deserialize<TObj>(_BsonReader);
         _BsonReader.Close();
         return _Return;
      }
   }

 

   public class RemoteFunction
   {
      public string Function = "";
     // public Dictionary<string, object> Arguments = new Dictionary<string, object>();
      public List<string> testlist = new List<string>();
      public string ReturnValue;
      public RemoteFunction()
      {
        // testlist.Add("haha");
      }
   }

 

 


 

Jan 13, 2010 at 6:47 AM

 

If I add _JsonSerializer.PreserveReferencesHandling = PreserveReferencesHandling.All; to my Serialize() method - it seems to work.

(but only if I have one collection in my RemoteFunction class - so, a List<> and Dictionary<> won't work, but just a List<> or Dictionary<> will work)

 

 

 

 

 

Jan 14, 2010 at 6:11 PM
Edited Jan 14, 2010 at 6:19 PM

I am having the same problem. 'Unexpected BsonType value: 0'

I tried the suggestion to add PreserveReferencesHandling.All and I am still getting the same error.

 

Thanks for any help people can offer,

Shane

 

    class Product
    {
        public DateTime ExpiryDate { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public List<string> Sizes { get; set; }
    }

Main
{
  Product p = new Product();
  p.ExpiryDate = DateTime.Parse("2009-04-05T14:45:00Z");
  p.Name = "Carlos' Spicy Wieners";
  p.Price = 9.95m;
  p.Sizes = new List<string>();

  var b1 = p.SerializeMe();
  Console.WriteLine(b1.Length);
  Console.WriteLine(b1.DeserializeMe<Product>().Name);
}


  /// <summary>
  /// Helper method to serialize an object to a byte array
  /// </summary>
  /// <param name="value"></param>
  /// <returns></returns>
  public static byte[] SerializeMe(this object value)
  {
    //Serialize product to BSON
    var ms = new MemoryStream();
    var writer = new BsonWriter(ms);
    var serializer = new JsonSerializer();
    serializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
    serializer.Serialize(writer, value);

    ms.Close();
    writer.Close();

    return ms.ToArray();
  }

  /// <summary>
  /// Helper method to deserialize a byte array to an object of type T
  /// </summary>
  /// <typeparam name="T"></typeparam>
  /// <param name="value"></param>
  /// <returns></returns>
  public static T DeserializeMe<T>(this byte[] value)
  {
    var serializer = new JsonSerializer();
    serializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
    var reader = new BsonReader(new MemoryStream(value));
    var returnObj = serializer.Deserialize<T>(reader);

    reader.Close();

    return returnObj;
  }

Jan 14, 2010 at 8:06 PM

I've done some searching through the code and I have found where the problem lies. When no elements are in either a List<> or a Dictionary<> nothing gets written inside the start and end bounds. This causes a problem when the BsonReader tries to identify the first element (which happens to be the end bound) and is translated to a BsonType of 0. I'm still looking into a fix but maybe this information can assist others atm.

 

JsonSerializerInteralWriter.cs (Lines 381-413: List<> creation,  Lines 443-478: Dictionary<> creation)

Jan 14, 2010 at 9:41 PM
Edited Jan 14, 2010 at 10:12 PM

I seem to have found a solution only for the case of Lists and Dictionaries containing primitive types.  If they are made up of objects then it still doesn't work.

 

BsonReader.cs (Lines 268-285)

 

    private bool ReadNormal()
    {
      switch (CurrentState)
      {
        case State.Start:
          {
            JsonToken token = (!_rootTypeIsArray) ? JsonToken.StartObject : JsonToken.StartArray;
            JTokenType type = (!_rootTypeIsArray) ? JTokenType.Object : JTokenType.Array;

            SetToken(token);
            ContainerContext newContext = new ContainerContext(type);
            PushContext(newContext);
            newContext.Length = ReadInt32();
            return true;
          }
        //case State.ObjectStart:
        //  {
        //    SetToken(JsonToken.PropertyName, ReadElement());
        //    return true;
        //  }
        case State.Complete:
        case State.Closed:
          return false;
        case State.Property:
          {
            ReadType(_currentElementType);
            return true;
          }
        case State.ObjectStart:
        case State.ArrayStart:
          //ReadElement();
          //ReadType(_currentElementType);
          //return true;
        case State.PostValue:
          ContainerContext context = _currentContext;
          if (context == null)
            return false;

          int lengthMinusEnd = context.Length - 1;

          if (context.Position < lengthMinusEnd)
          {
            if (context.Type == JTokenType.Array)
            {
              ReadElement();
              ReadType(_currentElementType);
              return true;
            }
            else
            {
              SetToken(JsonToken.PropertyName, ReadElement());
              return true;
            }
          }
          else if (context.Position == lengthMinusEnd)
          {
            if (ReadByte() != 0)
              throw new JsonReaderException("Unexpected end of object byte value.");

            PopContext();
            if (_currentContext != null)
              MovePosition(context.Length);

            JsonToken endToken = (context.Type == JTokenType.Object) ? JsonToken.EndObject : JsonToken.EndArray;
            SetToken(endToken);
            return true;
          }
          else
          {
            throw new JsonReaderException("Read past end of current container context.");
          }
        case State.ConstructorStart:
          break;
        case State.Constructor:
          break;
        case State.Error:
          break;
        case State.Finished:
          break;
        default:
          throw new ArgumentOutOfRangeException();
      }

      return false;
    }

 

 

Coordinator
Jan 16, 2010 at 5:31 AM

stewsha I've made your change to the source code and added some unit tests around it.

You said there are situations where the bug could still occur, could you give me an example? As far as I can test the changes made should eliminate the issue regardless of the List/Dictionary type.

Jan 18, 2010 at 2:32 PM
JamesNK wrote:

stewsha I've made your change to the source code and added some unit tests around it.

You said there are situations where the bug could still occur, could you give me an example? As far as I can test the changes made should eliminate the issue regardless of the List/Dictionary type.

It seems what I thought was a problem wasn't. I was trying to deserialize a List<> without passing the proper param that a List<> was being deserialized at the base.