Long ago, I set a coding standard for my app that all actions returning JSON would have their results put into a top-level wrapper object:
var result = {
success: false,
message: 'Something went wrong',
data: {} // or []
}
That has worked well, and provided me with good code standardization happiness.
Today, however, I realized that my server-side code assumes that it always gets to do the full serialization of what's getting returned. Now I would like to serialize one of these guys where the "data" payload is already a well-formed JSON string of its own.
This is the general pattern that had been working:
bool success = false;
string message = "Something went wrong";
object jsonData = "[{\"id\":\"0\",\"value\":\"1234\"}]"; // Broken
dynamic finalData = new { success = success, message = message, data = jsonData };
JsonResult output = new JsonResult
{
Data = finalData,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxJsonLength = int.MaxValue
};
return output;
Where it breaks is that the "data" element will be received as a string when it gets to the browser, and not as the proper JSON object (or array in the example above) it should be.
Is there some way I can decorate a property with an attribute that says "serialize as raw", or am I in the realm of writing a custom JSON serializer to make this work?
Here's what I ended up with....
// Wrap "String" in a container class
public class JsonStringWrapper
{
// Hey Microsoft - This is where it would be nice if "String" wasn't marked "sealed"
public string theString { get; set; }
public JsonStringWrapper() { }
public JsonStringWrapper(string stringToWrap) { theString = stringToWrap; }
}
// Custom JsonConverter that will just dump the raw string into
// the serialization process. Loosely based on:
// http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
public class JsonStringWrapperConverter : JsonConverter
{
private readonly Type _type = typeof(JsonStringWrapper);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
string rawValue = ((JsonStringWrapper)value).theString;
writer.WriteRawValue((rawValue == null) ? "null" : rawValue);
}
}
public override bool CanWrite
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _type == objectType;
}
}
// Custom JsonResult that will use the converter above, largely based on:
// http://stackoverflow.com/questions/17244774/proper-json-serialization-in-mvc-4
public class PreSerializedJsonResult : JsonResult
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new JsonStringWrapperConverter() }
};
public override void ExecuteResult(ControllerContext context)
{
if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("GET request not allowed");
}
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(this.ContentType) ? this.ContentType : "application/json";
if (this.ContentEncoding != null)
{
response.ContentEncoding = this.ContentEncoding;
}
if (this.Data == null)
{
return;
}
response.Write(JsonConvert.SerializeObject(this.Data, Settings));
}
}
// My base controller method that overrides Json()...
protected JsonResult Json(string message, object data)
{
PreSerializedJsonResult output = new PreSerializedJsonResult();
object finalData = (data is string && (new char[] { '[', '{' }.Contains(((string)data).First())))
? new JsonStringWrapper(data as string)
: data;
output.Data = new
{
success = string.IsNullOrEmpty(message),
message = message,
data = finalData
};
output.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
output.MaxJsonLength = int.MaxValue;
return output;
}
// Aaaand finally, here's how it might get called from an Action method:
...
return Json("This was a failure", null);
...
return Json(null, yourJsonStringVariableHere);
With this, I'm not doing any Json parsing on the server. My string comes out of the database and goes straight to the client without MVC touching it.
EDIT: Updated version now also supports serializing objects that have individual properties somewhere in their hierarchy that are of type JsonStringWrapper. This is useful in my scenario to support a "hybrid" model. If object A has a property B that is one of my pre-baked JSON strings, the code above will properly handle that.