Versatility of JSV – Late-bound objects

July 4th, 2010 by mythz Leave a reply »

New! Benchmarks graphs are now available to better visualize the performance of ServiceStack’s JSON and JSV text serializers.

As there have been a few people trying to use TypeSerializer in dynamic situations, I thought I’d put together a post detailing some restrictions and highlighting the kind of use-case scenarios that is possible with TypeSerializer and its JSV format.

Some of the goals for the JSV format was to be both compact in size and resilient to versioning and schema changes. With these goals in mind, a conscience design decision was made to not include any type information with the serialized payload. So the way that JSV does its de-serializing is by coercing the JSV payload into the type specified by the user. This can be seen in the API provided by the TypeSerializer class allowing the client to deserialize based on a runtime or static generic type:

T DeserializeFromString<T>(string value)
object DeserializeFromString(string value, Type type)

The consequences of this means the user in one way or another must supply the type information although at the same time it allows the same JSV payload to be deserialized back into different types. For example every POCO type can be deserialized back into a Dictionary<string,string> which is useful when you want to still access the data but for whatever reason do not have the type that created it. This also allows for some interesting versioning possibilities in which the format can withstand large changes in its schemas as seen in the article Painless data migrations with schema-less NoSQL datastores and Redis.

Beyond normal serialization of DTO types, TypeSerializer is also able to serialize deep heirachys and Interface types as well as ‘late-bound objects’. The problem with trying to deserialize a late-bound object (i.e. a property with an object type) is that TypeSerializer doesn’t know what type to de-serialize it back into – and since a string is a valid object, will simply populate the object property with the string contents of the serialized property value.

With this in mind, the best way to deserialize a POCO type with a dynamic object property is to serialize the Type information yourself along with the payload. Of course it is best to highlight what this means with an example.

The example below shows how you can serialize a message with a dynamic object payload and have it deserialize back into a DynamicMessage as well as alternate GenericMessage<T> and a StrictMessage types sharing a similar definition – all as expected, without any data loss.

public class DynamicMessage : IMessageHeaders
{
	public Guid Id { get; set; }
	public string ReplyTo { get; set; }
	public int Priority { get; set; }
	public int RetryAttempts { get; set; }
	public object Body { get; set; }

	public Type Type { get; set; }
	public object GetBody()
	{
		//When deserialized this.Body is a string so use the serilaized
		//this.Type to deserialize it back into the original type
		return this.Body is string
		? TypeSerializer.DeserializeFromString((string)this.Body, this.Type)
		: this.Body;
	}
}

public class GenericMessage<T> : IMessageHeaders
{
	public Guid Id { get; set; }
	public string ReplyTo { get; set; }
	public int Priority { get; set; }
	public int RetryAttempts { get; set; }
	public T Body { get; set; }
}

public class StrictMessage : IMessageHeaders
{
	public Guid Id { get; set; }
	public string ReplyTo { get; set; }
	public int Priority { get; set; }
	public int RetryAttempts { get; set; }
	public MessageBody Body { get; set; }
}

public class MessageBody
{
	public MessageBody()
	{
		this.Arguments = new List<string>();
	}

	public string Action { get; set; }
	public List<string> Arguments { get; set; }
}

/// Common interface not required, used only to simplify validation
public interface IMessageHeaders
{
	Guid Id { get; set; }
	string ReplyTo { get; set; }
	int Priority { get; set; }
	int RetryAttempts { get; set; }
}

[TestFixture]
public class DynamicMessageTests
{
	[Test]
	public void Can_deserialize_between_dynamic_generic_and_strict_messages()
	{
		var original = new DynamicMessage
		{
			Id = Guid.NewGuid(),
			Priority = 3,
			ReplyTo = "http://path/to/reply.svc",
			RetryAttempts = 1,
			Type = typeof(MessageBody),
			Body = new MessageBody
			{
				Action = "Alphabet",
				Arguments = { "a", "b", "c" }
			}
		};

		var jsv = TypeSerializer.SerializeToString(original);
		var dynamicType = TypeSerializer.DeserializeFromString<DynamicMessage>(jsv);
		var genericType = TypeSerializer.DeserializeFromString<GenericMessage<MessageBody>>(jsv);
		var strictType = TypeSerializer.DeserializeFromString<StrictMessage>(jsv);

		AssertHeadersAreEqual(dynamicType, original);
		AssertBodyIsEqual(dynamicType.GetBody(), (MessageBody)original.Body);

		AssertHeadersAreEqual(genericType, original);
		AssertBodyIsEqual(genericType.Body, (MessageBody)original.Body);

		AssertHeadersAreEqual(strictType, original);
		AssertBodyIsEqual(strictType.Body, (MessageBody)original.Body);

		//Using T.Dump() ext method to view output
		Console.WriteLine(strictType.Dump());
		/* Output:
		 {
			Id: 891653ea2d0a4626ab0623fc2dc9dce1,
			ReplyTo: http://path/to/reply.svc,
			Priority: 3,
			RetryAttempts: 1,
			Body:
			{
				Action: Alphabet,
				Arguments:
				[
					a,
					b,
					c
				]
			}
		}
		*/
	}

	public void AssertHeadersAreEqual(IMessageHeaders actual, IMessageHeaders expected)
	{
		Assert.That(actual.Id, Is.EqualTo(expected.Id));
		Assert.That(actual.ReplyTo, Is.EqualTo(expected.ReplyTo));
		Assert.That(actual.Priority, Is.EqualTo(expected.Priority));
		Assert.That(actual.RetryAttempts, Is.EqualTo(expected.RetryAttempts));
	}

	public void AssertBodyIsEqual(object actual, MessageBody expected)
	{
		var actualBody = actual as MessageBody;
		Assert.That(actualBody, Is.Not.Null);
		Assert.That(actualBody.Action, Is.EqualTo(expected.Action));
		Assert.That(actualBody.Arguments, Is.EquivalentTo(expected.Arguments));
	}
}

The source of this runnable example can be found as part of TypeSerializer’s test suite in the DynamicMessageTests.cs test class. Some more dynamic examples showing advanced usages of TypeSerializer can be found in the ComplexObjectGraphTest.cs class within the same directory.

.

Advertisement

3 comments

  1. Matt Johnson says:

    Great Article, just what I needed. But I do have one question. I’m porting over Resque to .Net. At least trying. I have Job Objects that get serialized and queued in Redis. Like your article, when I dequeue and item, I need to deserialize the Job object back to the correct type, so I’m saying the type information. Problem is, the type that was used exists outside of the Resque.dll, and TypeSerializer.CanCreateFromString(job.JobType); returns false. Any ideas on how to get around this. My thought is that each user of system will need their own job objects to perform what it is they need to. When the worker pulls the job out of the queue, it will need to deserialize it. Any ideas?

  2. mythz says:

    Hi Matt,

    If you are saving and restoring Type information, I would ignore ‘.CanCreateFromString()’ and simply attempt to deserialize it (in a try/catch{}). Since I use “Type.GetType(assemblyQualifiedName);” behind the scenes, the .NET runtime should be able to automatically load the required assembly as long as the type is in a .dll that’s in the bin/ directory.

    IMHO I would only ever put the one type in the same queue as it makes it much easier to handle. In which case you wouldn’t have to persist the Type information since you already know it based on the queue name.

    I have already started my own message queue implementation with a redis backend which you may be able to get some ideas about here: http://bit.ly/cZhFit
    And some tests for it here: http://bit.ly/9LGkVm as well as an InMemory implementation.

  3. JP says:

    Hi Demis,

    This looks really useful, thanks. I have a follow-up question though. In a project that uses ServiceStack as its webservice framework, I have a DTO with a member of type List.

    I’m wondering if it’s possible to let ServiceStack take care of the standard (de)serialization of the DTO as it does by default, while making sure that this special serialization is used for that particular member. Is there any way I can influence the behavior of the out-of-the-box serialization in ServiceStack?

    In other words, is there a way to plug into the default serialization, by using attributes or configuration?

    Thanks for any info.

Leave a Reply