Here's a scenario that keeps popping up for me occasionally: In a number of situations I've needed to quickly and compactly serialize an object to a string. There are a million serializers out there from XML to binary, to JSON and even the somewhat compact LosFormatter in ASP.NET. However, all the built-in serialization mechanisms are pretty bloated and verbose especially when sending output to strings.
An Example for Micro-Serialization
Some scenarios where this makes sense is for compact configuration storage or token generation for application level storage. A common place where I need this is for cookies that pack a few values together or for a forms authentication ticket value. For example, I have a UserState object with common properties that stores UserId, User Name, Display Name, email address and a security level in a small object that is carried through with the forms authentication cookie - this avoids having to hit the database (or Session which I don't use) for user information on every hit.
The UserState object is very small and so it seems like overkill to throw a full JSON or XML serialization at it. Furthermore the XML and JSON sizes are pretty large. For my UserState object I used to actually hand code a 'custom' serialization mechanism which is trivial simple and based on string.Split() and string.Join() with a pipe symbol.
To demonstrate that scenario - which I have implemented on numerous other occasion - here's the code:
///
/// Exports a short string list of Id, Email, Name separated by |
///
public override string ToString()
{
return string.Join(STR_Seperator, new string[] {
UserId,
Name,
IsAdmin.ToString(),
Email });
}
///
/// Imports Id, Email and Name from a | separated string
///
public bool FromString(string itemString)
{
if (string.IsNullOrEmpty(itemString))
return false;
var state = CreateFromString(itemString);
if (state == null)
return false;
UserId = state.UserId;
Email = state.Email;
Name = state.Name;
IsAdmin = state.IsAdmin;
return true;
}
///
/// Creates an instance of a userstate object from serialized
/// data.
///
/// IsEmpty() will return true if data was not loaded. A
/// UserData object is always returned.
///
public static UserState CreateFromString(string userData)
{
if (string.IsNullOrEmpty(userData))
return null;
string[] strings = userData.Split(new string[1] {STR_Seperator}, StringSplitOptions.None );
if (strings.Length < 4)
return null;
var userState = new UserState();
userState.UserId = strings[0];
userState.Name = strings[1];
userState.IsAdmin = strings[2] == "True";
userState.Email = strings[3];
return userState;
}
Simple enough, but it ends being a bit of non-flexible code as the properties have to be stored and retrieved in exactly the right order. Also if the class adds a property I have to manually add any properties in both ToString() and CreateFromString(). Nevertheless, this is efficient since it is handcoded and I've written code like this on a number of occasions, often enough to warrant a more generic solution.
Making it Generic: StringSerializer
Besides violating the DRY priniciple, there's also the issue of this code not being very flexible. If I decide to add a property to this object the serialization routines have to be updated. Or - more likely perhaps in an object like this, if it's subclassed there's then no easy way to add additional properties to serialize.
So today I spent a little time creating a simple generic component that basically provides this behavior as a reusable component. Here's an implementation of a simple StringSerializer:
///
/// A very simple flat object serializer that can be used
/// for intra application serialization. It creates a very compact
/// positional string of properties.
/// Only serializes top level properties, with no nesting support
/// and only simple properties or those with a type converter are
/// supported. Complex properties or non-two way type convertered
/// values are ignored.
///
/// Creates strings in the format of:
/// Rick|rstrahl@west-wind.com|1|True|3/29/2013 1:32:31 PM|1
///
///
/// This component is meant for intra application serialization of
/// very compact objects. A common use case is for state serialization
/// for cookies or a Forms Authentication ticket to minimize the amount
/// of space used - the output produced here contains only the actual
/// data, no property info or validation like other serialization formats.
/// Use only on small objects when size and speed matter otherwise use
/// a JSON/XML/Binary serializer or the ASP.NET LosFormatter object.
///
public static class StringSerializer
{
private const string Seperator_Replace_String = "-@-";
///
/// Serializes a flat object's properties into a String
/// separated by a separator character/string. Only
/// top level properties are serialized.
///
///
/// Only serializes top level properties, with no nesting support
/// and only simple properties or those with a type converter are
/// 'serialized'. All other property types use ToString().
///
/// The object to serialize
/// Optional separator character or string. Default is |
///
public static string SerializeObject(object objectToSerialize, string separator = null)
{
if (separator == null)
separator = "|";
if (objectToSerialize == null)
return "null";
var properties = objectToSerialize.GetType()
.GetProperties(BindingFlags.Instance |
BindingFlags.Public);
var values = new List<string>();
for (int i = 0; i < properties.Length; i++)
{
var pi = properties[i];
// don't store read/write-only data
if (!pi.CanRead && !pi.CanWrite)
continue;
object value = pi.GetValue(objectToSerialize, null);
string stringValue = "null";
if (value != null)
{
if (value is string)
{
stringValue = (string)value;
if (stringValue.Contains(separator))
stringValue = stringValue.Replace(separator, Seperator_Replace_String);
}
else
stringValue = ReflectionUtils.TypedValueToString(value, unsupportedReturn: "null");
}
values.Add(stringValue);
}
if (values.Count < 0)
// empty object (no properties)
return string.Empty;
return string.Join(separator, values.ToArray());
}
///
/// Deserializes an object previously serialized by SerializeObject.
///
///
///
///
///
public static object DeserializeObject(string serialized, Type type, string separator = null)
{
if (serialized == "null")
return null;
if (separator == null)
separator = "|";
object inst = ReflectionUtils.CreateInstanceFromType(type);
var properties = inst.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
string[] tokens = serialized.Split(new string[] { separator }, StringSplitOptions.None);
for (int i = 0; i < properties.Length; i++)
{
string token = tokens[i];
var prop = properties[i];
// don't store read/write-only data
if (!prop.CanRead && !prop.CanWrite)
continue;
token = token.Replace(Seperator_Replace_String, separator);
object value = null;
if (token != null)
{
try
{
value = ReflectionUtils.StringToTypedValue(token, prop.PropertyType);
}
catch (InvalidCastException ex)
{
// skip over unsupported types
}
}
prop.SetValue(inst, value, null);
}
return inst;
}
///
/// Deserializes an object serialized with SerializeObject.
///
///
///
///
///
public static T Deserialize(string serialized, string separator = null)
where T : class, new()
{
return DeserializeObject(serialized, typeof(T), separator) as T;
}
}
Note that there are two dependencies on ReflectionUtils and the TypedValueToString and StringToTypedValue which handle the string type conversions. They're included in a support file linked at the bottom of this post.
With this object in place I can now re-write the UserState object's built in self serialization like this:
public override string ToString()
{
return StringSerializer.SerializeObject(this);
}
public bool FromString(string itemString)
{
if (string.IsNullOrEmpty(itemString))
return false;
var state = CreateFromString(itemString);
if (state == null)
return false;
// copy the properties
DataUtils.CopyObjectData(state, this);
return true;
}
public static UserState CreateFromString(string userData)
{
if (string.IsNullOrEmpty(userData))
return null;
return StringSerializer.Deserialize<UserState>(userData);
}
public static UserState CreateFromFormsAuthTicket()
{
return CreateFromString(((FormsIdentity)HttpContext.Current.User.Identity).Ticket.UserData);
}
The object now is uses the single StringSerializer methods to serialize and deserialize and the object is now more flexible in that it can automatically deal with additional properties that are added, perhaps even in a subclass.
To use the UserState object is super simple now. For example, in my base MVC Controller I can now easily attach the UserState object to the controller and the ViewBag:
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
// Grab the user's login information from FormsAuth
if (this.User.Identity != null && this.User.Identity is FormsIdentity)
this.UserState = UserState.CreateFromFormsAuthTicket();
else
this.UserState = new UserState();
// have to explicitly add this so Master can see untyped value
ViewBag.UserSate = this.UserState;
ViewBag.ErrorDisplay = this.ErrorDisplay;
}
making the UserState easily available anywhere in the app. The user data is written once when user logs or changes his profile info but otherwise the UserState is only read on each hit and made available to the app when logged in users are present. It's a great way to store basic information about a user without having to hit the database (as you have to with MemberShip in ASP.NET).
Limitations
As the comments describe, StringSerializer is not meant to be a full featured serializer. If you need to serialize large and nested objects or you need to share the data with other applications, this is probably not what you want to use. Use the JSON.NET serializer (because it's fast, robust and reasonably compact) or the Xml Serializer instread. StringSerializer has no support for nested objects and it only works the built-in .NET system types and anything that supports two-way ValueConverter string conversion.
StringSerializer makes a few assumptions: It requires that the fields serialized are desterilized in exactly the same order - IOW, deserialization expects the type signature on the other end to be the same as the input. Since there are no property names or any other kind of meta-data in the output, the data is small, but also is set up in a fixed format that must be the same on both ends. If you add properties the format breaks - hence the point about intra-app serialization - it's not meant as a portable format to share amongst machines or platforms or be version aware.
When it makes Sense
This serializer is fast and produces very compact output for small, flat objects and if all of these are important then it's a good fit. Serializing the 5 fields as I'm doing in the UserState object using JSON.NET would be overkill, produce a larger string and take more time to process.
The string data created is small - my UserState object serialized with this is 85 bytes in length which is basically the string value representation for each property plus the separators. JSON.NET formatted that same object to 166 bytes. The Xml Serializer to 379. The LosFormatter created a whopping 604 bytes (and required [Serializable]). StringSerializer also runs faster than any of the others I tried - a 1000 iterations on my machine take 5ms for the StringSerializer, 8ms for JSON.NET, 11ms for the XML Serializer and 16 for the LosFormatter . The perf of all of these is small enough to not matter, but the output size is significantly smaller for the StringSerializer which in the case of cookie or FormsAuth usage is vital.
It's not an everyday kind of component, but rather fits a special use case scenario, but one that I've run into often enough to warrant creating a reusable class for. Anyway I hope some of you find this useful.
Resources
Other Posts you might also like