Search code examples
c#jsonservicestackservicestack-text

ServiceStack OnDeserialized Equivalent


I am deserialize a websocket message in real time. In the message (string of json) I receive there is a unix timestamp (long). As soon as each object is deserialized I need it to call a method ASAP so that I can capture the delay between the time the message was sent and received. With Json.NET that was simple I just added this method to my DataContract class:

[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
    Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(TimestampLong).LocalDateTime;
    Delay = DateTime.Now - Timestamp;
}

I would much prefer to use ServiceStack's deserializer going forward for various reasons but I can't seem to figure out a way to do this. I did find this StackOverflow post from almost 10 years ago but I'm hoping that's changed or that there is a workaround that I can use.


Solution

  • ServiceStack.Text doesn't support these attributes by default, but you can implement serialization callbacks with Custom Type Configuration, e.g:

    JsConfig<MyType>.OnDeserializedFn = o =>
    {
        o.OnDeserialized(null);
        return o;
    };
    

    The SerializationHookTests.cs shows how you can use the Type Filters to wire up these callbacks for a type using this helper:

    static void AddSerializeHooksForType<T>()
    {
        Type type = typeof(T);
        System.Reflection.MethodInfo[] typeMethods = type
          .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        var onSerializingMethods = typeMethods.Where(m => 
            m.GetCustomAttributes(typeof(OnSerializingAttribute), true).Length > 0);
        var OnDeserializedMethods = typeMethods.Where(m => 
            m.GetCustomAttributes(typeof(OnDeserializedAttribute), true).Length > 0);
        var OnSerializedMethods = typeMethods.Where(m => 
            m.GetCustomAttributes(typeof(OnSerializedAttribute), true).Length > 0);
        Object[] Parameters = { null };
    
        if (onSerializingMethods.Any()) {
            ServiceStack.Text.JsConfig<T>.OnSerializingFn = s => {
                foreach (var method in onSerializingMethods)
                    method.Invoke(s, Parameters);
                return s;
            };
        }
        if (OnSerializedMethods.Any()) {
            ServiceStack.Text.JsConfig<T>.OnSerializedFn = s => {
                foreach (var method in OnSerializedMethods)
                    method.Invoke(s, Parameters);
            };
        }
        if (OnDeserializedMethods.Any()) {
            ServiceStack.Text.JsConfig<T>.OnDeserializedFn = s => {
                foreach (var method in OnDeserializedMethods)
                    method.Invoke(s, Parameters);
                return s;
            };
        }
    }
    

    Which you can wire up for a type with:

    AddSerializeHooksForType<HookTest>();
    

    Where it will call the desired callbacks, e.g:

    public class HookTest
    {
        /// <summary>
        /// Will be executed when deserializing starts
        /// </summary>
        [OnDeserializing]
        protected void OnDeserializing(StreamingContext ctx) { }
    
        /// <summary>
        /// Will be executed when deserializing finished
        /// </summary>
        [OnDeserialized]
        protected void OnDeserialized(StreamingContext ctx) { }
    
        /// <summary>
        /// Will be executed when serializing starts
        /// </summary>
        [OnSerializing]
        protected void OnSerializing(StreamingContext ctx) { }
    
        /// <summary>
        /// Will be executed when serializing finished
        /// </summary>
        [OnSerialized]
        protected void OnSerialized(StreamingContext ctx) { }
    }