繁体   English   中英

在MVC操作输出中自定义JSON序列化

[英]Customizing JSON serialization in MVC action output

很久以前,我为我的应用程序设置了编码标准,所有返回JSON的操作都将其结果放入顶级包装对象中:

var result = {
    success: false,
    message: 'Something went wrong',
    data: {} // or []
}

效果很好,并为我提供了良好的代码标准化幸福感。

但是,今天,我意识到我的服务器端代码假定它总是对返回的内容进行完全序列化。 现在,我想对其中一个“数据”有效载荷已经是其自身格式正确的JSON字符串的家伙进行序列化。

这是一直起作用的一般模式:

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;

中断之处在于,“ data”元素在到达浏览器时将作为字符串接收,而不是应作为适当的JSON对象(或上例中的数组)接收。

是否可以通过某种方式用属性表示“以原始方式串行化”来装饰属性,或者我是在编写自定义JSON序列化程序来实现此目的?

您要对其进行两次序列化(jsonData +输出)。 您不能这样做,只能期望将其反序列化一次(输出)。

您可以将动态对象中的“数据”对象设置为真正的c#对象,这将起作用。 或者,您可以将属性重命名为“ jsonData”:

dynamic finalData = new { success = success, message = message, jsonData = jsonData };

...因此它反映了您的实际工作:)。

您可以通过使用Newtonsoft的JsonWriter类自己形成JSON包来实现此目的。 它看起来像这样:

using(var textWriter = new StringWriter())
using(var jsonWriter = new JsonTextWriter(textWriter))
{
   jsonWriter.WriteStartObject();

   jsonWriter.WritePropertyName("success");
   jsonWriter.WriteValue(success);

   jsonWriter.WritePropertyName("message");
   jsonWriter.WriteValue(message);

   jsonWriter.WritePropertyName("data");
   jsonWriter.WriteRaw(jsonData);

   jsonWriter.WriteEndObject();

   var result = new ContentResult();
   result.Content = textWriter.ToString();
   result.ContentType = "application/json";
   return result;
}

我认为您只需要使用JSON序列化程序(如NewtonSoft)将从SQL表返回的字符串序列化为一个对象。

bool success = false;
string message = "Something went wrong";
string rawData = "[{\"id\":\"0\",\"value\":\"1234\"}]";  // Broken
object jsonData = JsonConvert.DeserializeObject<dynamic>(rawData);

dynamic finalData = new { success = success, message = message, data = jsonData };

JsonResult output = new JsonResult
{
    Data = finalData,
    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
    MaxJsonLength = int.MaxValue
};
return output;

这就是我最终得到的...

// 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);

这样,我就不会在服务器上进行任何Json解析。 我的字符串从数据库中出来,直接到达客户端,而MVC却没有碰它。

编辑:更新的版本现在还支持序列化对象,这些对象在其层次结构中某个位置具有JsonStringWrapper类型的各个属性。 在我的场景中,这对于支持“混合”模型很有用。 如果对象A的属性B是我预先烘焙的JSON字符串之一,则上面的代码将正确处理该属性。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM