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.
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 = ... } );