Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EasyNetQ Json.NET JObject serialization - surprising behavior #460

Closed
ojintoad opened this issue Jun 24, 2015 · 5 comments
Closed

EasyNetQ Json.NET JObject serialization - surprising behavior #460

ojintoad opened this issue Jun 24, 2015 · 5 comments

Comments

@ojintoad
Copy link

I'm seeing somewhat weird behavior when trying to send Json.Net JObjects through EasyNetQ's Default Serializer.

Here's a contrived example:

using EasyNetQ;
using Newtonsoft.Json.Linq;

namespace TestEasyNetQSerializationBug
{
    class Program
    {
        static void Main(string[] args)
        {
            var obj = JObject.Parse(@"{""data"":""data""}");
            using (var bus = RabbitHutch.CreateBus("amqp://localhost"))
            {
                bus.Publish(obj);
            }
        }
    }
}

The object that gets published is:

{"data":{"$type":"Newtonsoft.Json.Linq.JValue, Newtonsoft.Json","$values":[]}}

Which leaves me sort of 😞

So I saw this issue: #237

Where it was suggested to a user to reimplement the json serializer. So I did that:

static void Main(string[] args)
        {
            var obj = JObject.Parse(@"{""data"":""data""}");
            using (var bus = RabbitHutch.CreateBus("amqp://localhost", serviceRegister => serviceRegister.Register<ISerializer>(
                    serviceProvider => new MyJsonSerializer(new TypeNameSerializer()))
                ))
            {
                bus.Publish(obj);
            }
        }

        public class MyJsonSerializer : ISerializer
        {
            private readonly ITypeNameSerializer typeNameSerializer;
            private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto
            };
            public MyJsonSerializer(ITypeNameSerializer typeNameSerializer)
            {
                this.typeNameSerializer = typeNameSerializer;
            }
            public byte[] MessageToBytes<T>(T message) where T : class
            {
                return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message, serializerSettings));
            }
            public T BytesToMessage<T>(byte[] bytes)
            {
                return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(bytes), serializerSettings);
            }
            public object BytesToMessage(string typeName, byte[] bytes)
            {
                var type = typeNameSerializer.DeSerialize(typeName);
                return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bytes), type, serializerSettings);
            }
        }

And out comes:

{"data":"data"}

Which leaves me 😀 .

The thing is that Serializer is basically the EasyNetQ serializer without EasyNetQ dependencies present so it will compile properly.

I'm happy to have a solution, but it just seems like weird behavior that it's behaving this way with what seems like identical code.

For reference:
Using Visual Studio 2013, Rabbit MQ 3.4.1 locally installed on Windows
Code snippets tested with: EasyNetQ 0.50.1.392, Json.NET 7.0.1

@mikehadlow
Copy link
Contributor

Don't forget that EasyNetQ serializes .NET types as JSON to send them over
the wire, then de-serializes them at the other end. Here you start with
JSON, so it seems redundant to do any serialization at all. Why don't you
use the Advanced API
https://github.com/EasyNetQ/EasyNetQ/wiki/The-Advanced-API : Something like:

var properties = new MessageProperties();
var body = Encoding.UTF8.GetBytes("{ "data": "data" }");
advancedBus.Publish(Exchange.GetDefault, queueName, properties, body);

On Wed, Jun 24, 2015 at 9:13 PM, Todd Ogin notifications@github.com wrote:

I'm seeing somewhat weird behavior when trying to send Json.Net JObjects
through EasyNetQ's Default Serializer.

Here's a contrived example:

using EasyNetQ;
using Newtonsoft.Json.Linq;

namespace TestEasyNetQSerializationBug
{
class Program
{
static void Main(string[] args)
{
var obj = JObject.Parse(@"{""data"":""data""}");
using (var bus = RabbitHutch.CreateBus("amqp://localhost"))
{
bus.Publish(obj);
}
}
}
}

The object that gets published is:

{"data":{"$type":"Newtonsoft.Json.Linq.JValue, Newtonsoft.Json","$values":[]}}

Which leaves me sort of [image: 😞]

So I saw this issue: #237
#237

Where it was suggested to a user to reimplement the json serializer. So I
did that:

static void Main(string[] args)
{
var obj = JObject.Parse(@"{""data"":""data""}");
using (var bus = RabbitHutch.CreateBus("amqp://localhost", serviceRegister => serviceRegister.Register(
serviceProvider => new MyJsonSerializer(new TypeNameSerializer()))
))
{
bus.Publish(obj);
}
}

    public class MyJsonSerializer : ISerializer
    {
        private readonly ITypeNameSerializer typeNameSerializer;
        private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto
        };
        public MyJsonSerializer(ITypeNameSerializer typeNameSerializer)
        {
            this.typeNameSerializer = typeNameSerializer;
        }
        public byte[] MessageToBytes<T>(T message) where T : class
        {
            return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message, serializerSettings));
        }
        public T BytesToMessage<T>(byte[] bytes)
        {
            return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(bytes), serializerSettings);
        }
        public object BytesToMessage(string typeName, byte[] bytes)
        {
            var type = typeNameSerializer.DeSerialize(typeName);
            return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bytes), type, serializerSettings);
        }
    }

And out comes:

{"data":"data"}

Which leaves me [image: 😀] .

The thing is that Serializer is basically the EasyNetQ serializer
https://github.com/EasyNetQ/EasyNetQ/blob/master/Source/EasyNetQ/JsonSerializer.cs
without EasyNetQ dependencies present so it will compile properly.

I'm happy to have a solution, but it just seems like weird behavior that
it's behaving this way with what seems like identical code.

For reference:
Using Visual Studio 2013, Rabbit MQ 3.4.1 locally installed on Windows
Code snippets tested with: EasyNetQ 0.50.1.392, Json.NET 7.0.1


Reply to this email directly or view it on GitHub
#460.

@codenaked
Copy link

The EasyNetQ use ilmerge to include the Newtonsoft.Json assembly now, so it doesn't have to have an external dependency on Newtonsoft. So internally, it uses it has it's own copy of JsonConvert, JObject, etc.

When you pass your JObject in the first example, the "version" of Newtonsoft used by the default serializer does not recognize it as it's own "version" JObject, because it's not (i.e. JsonConvert is the one in the EasyNetQ assembly, JObject is the one from Newtonsoft assembly). So it serializes it as a third-party type.

When you create your custom serializer you are effectively saying don't use the "version" of Newtonsoft in the EasyNetQ assembly. So now when you pass your JObject, the "version" of Newtonsoft used by your serializer does recognize it as it's own "version" JObject, so it can handle it better (i.e. JsonConvert and JObject are both from the Newtonsoft assembly).

@zidad
Copy link
Member

zidad commented Jun 25, 2015

@ojintoad why are you trying to publish instances of JObject, instead of "normal" objects? That seems strange, how would you consume them?

@ojintoad
Copy link
Author

To answer @mikehadlow and @zidad the example is contrived. The actual use case has the JObject as a property of a well defined type. Yes, we could serialize the jobject first but that involves handling this message as a special case. The serializer solution alleviates that.

The answer @codenaked provides makes sense. I certainly would prefer not having to add the serializer, but it sounds like EasyNetQ has made a decision about Json.NET that makes this required. I wasn't necessarily looking for a code change. The explanation provides sufficient justification for me to leave my workaround in and document it in our own code base so developers are informed of its purpose.

If there are other suggestions I'm happy to hear them, but I'm also happy with the response here and feel this issue can be closed.

@jhorbulyk
Copy link

I suppose one of the reasons why one would want EasyNetQ to serialize JObjects as one would expect is in the use case where one wants to mix structured and unstructured data on the message that gets passed to the bus. In @ojintoad's original example, imagine that instead of obj being a JObject it is a class of the following form:

public class SampleObject{
  public int structuredData1;
  public string structuredData2;
  public DateTime structuredData3;
  public JObject unstructuredData;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants