【问题标题】:Customizing JSON serialization in MVC action output在 MVC 操作输出中自定义 JSON 序列化
【发布时间】:2016-12-08 23:01:26
【问题描述】:

很久以前,我为我的应用设置了一个编码标准,所有返回 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 序列化程序来完成这项工作?

【问题讨论】:

  • 你必须使用json.parse。在此处阅读完整的详细信息developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 好吧,从技术上讲,您违反了自己的合同和标准。以前你期待一个object,现在你传递给它一个字符串。在我看来,这是一种不好的处理方式,但如果无法避免使用此字符串,您可以data = JsonSerializer.Deserialize(jsonData)。但请注意,您将序列化它只是为了稍后将其反序列化。
  • json.parse 在浏览器端。我正在尝试操纵服务器端的序列化。 Rob,您正是遇到了问题。我不想反序列化它,只是为了立即重新序列化它(实际上只是在已经非常好的 JSON 周围放置一个薄包装)。不过,我想我已经找到了一个解决方案,并将在明天发布。谢谢你们!

标签: c# json asp.net-mvc json-deserialization jsonresult


【解决方案1】:

您将其序列化两次(jsonData + 输出)。你不能这样做并且期望只反序列化一次(输出)。

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

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

...所以它反映了你真正在做什么:)。

【讨论】:

  • 谢谢,jvenema,感谢您的回复。我的麻烦是这不是一个真正的 C# 对象。它实际上是直接来自 SQL 作为 JSON 字符串。在我的中间层中,我什至没有与它相当的类型。我只是想让网络服务器成为一个传递。就是说-我想我已经基于几个其他线程将一个带有自定义序列化程序的解决方案组合在一起。如果可行,我将在明天发布我的想法。
【解决方案2】:

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

// 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 字符串之一,则上面的代码将正确处理。

【讨论】:

    【解决方案3】:

    您可以通过使用 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;
    }
    

    【讨论】:

      【解决方案4】:

      我认为您只需使用 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;
      

      【讨论】:

        猜你喜欢
        • 2016-01-07
        • 2013-03-05
        • 1970-01-01
        • 2014-07-08
        • 1970-01-01
        • 2011-02-25
        • 1970-01-01
        • 2014-05-02
        • 2014-10-31
        相关资源
        最近更新 更多