Search code examples
c#jsonserializationservicestackservicestack-text

ServiceStack's JSON deserializer parses invalid JSON


Given the invalid JSON text, { "foo" = "bar" }, the JSON deserializer built into ServiceStack will successfully decode this into the following DTO:

public class FooDto {
   public string Foo { get; set; }
}

Now, perhaps that's acceptable for some use cases because it looks kind of like a C# initializer, so maybe not actually parsing JSON is a feature for this library.

However, when given the invalid JSON text, { "foo" xxx "bar" }, no error is thrown and horrifyingly the Foo property is set to "xxx".

Is there a way to either (a) configure ServiceStack.Text to parse only JSON (instead of whatever non-standard variant it accepts by default) or (b) replace the serializer with, say, JSON.NET at the application level?

EDIT: It also looks like ServiceStack's deserialization behavior for its web routing code behaves differently from the behavior of ServiceState.Text.JsonSerializer which appears to return default(T) on an invalid parse.

EDIT 2 (Progress?):

appHost.SetConfig(new HostConfig { UseBclJsonSerializers = true });

Setting this option will cause ServiceStack to return a 400 Bad Request on malformed JSON, but unfortunately fails to deserialize the JSON into the given DTO. Maybe this is part of a solution?

SOLUTION:

Replacing ServiceStack's default JSON serializer with a custom "application/json" handler which in turn wrapped the JSON.NET serializer ended up solving the problem. I've included a copy of the solution code in an answer below. I hope it helps.


Solution

  • I ended up writing a custom JSON serializer wrapping the excellent JSON.NET library. This solution raises exceptions on invalid JSON and so returns 400 Bad Request as expected.

    Caveat: This implementation ignores the Accept-Charset header as well as the charset parameter of the Content-Type header, and instead assumes UTF8. If you're not able to assume UTF8 on the wire, you'll want to tweak this code.

    public class UseJsonDotNet : IPlugin
    {
        public JsonSerializerSettings Settings { get; set; }
    
        public void Register(IAppHost appHost)
        {
            appHost.ContentTypes.Register(
                "application/json",
                WriteObjectToStream,
                ReadObjectFromStream);
        }
    
        public void WriteObjectToStream(
            IRequest request, object response, Stream target)
        {
            var s = JsonConvert.SerializeObject(response, Formatting.None, Settings);
            using (var writer = new StreamWriter(target, Encoding.UTF8, 1024, true))
            {
                writer.Write(s);
            }
        }
    
        public object ReadObjectFromStream(Type type, Stream source)
        {
            using (var reader = new StreamReader(source, Encoding.UTF8))
            {
                var s = reader.ReadToEnd();
                var o = JsonConvert.DeserializeObject(s, type, Settings);
                return o;
            }
        }
    }
    

    To use it, just register it:

    Plugins.Add(new UseJsonDotNet { Settings = ... } );