Настройка сериализации JSON в выводе действия MVC

Давным-давно я установил стандарт кодирования для своего приложения, согласно которому все действия, возвращающие 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;

Где он ломается, так это в том, что элемент «данные» будет получен как строка, когда он попадет в браузер, а не как правильный объект JSON (или массив в приведенном выше примере), каким он должен быть.

Есть ли способ украсить свойство атрибутом, который говорит «сериализовать как необработанный», или я нахожусь в сфере написания собственного сериализатора JSON, чтобы заставить эту работу работать?


person Eric    schedule 08.12.2016    source источник
comment
Вы должны использовать json.parse. Подробнее читайте здесь developer.mozilla.org/ en-US/docs/Web/JavaScript/Reference/   -  person Pratik Gaikwad    schedule 09.12.2016
comment
Ну, технически вы нарушаете свой собственный контракт и стандарты. Раньше вы ожидали объект, теперь вы передаете ему строку. Мне кажется, что это плохой способ, но вы можете написать data = JsonSerializer.Deserialize(jsonData), если нет способа избежать этой строки. Однако обратите внимание, что вы будете сериализовать его только для того, чтобы потом десериализовать.   -  person Rob    schedule 09.12.2016
comment
json.parse находится на стороне браузера. Я пытаюсь манипулировать сериализацией на стороне сервера. Роб, вы точно разобрались с проблемой. Я не хочу ее десериализовать, просто чтобы затем немедленно повторно сериализовать (и действительно просто поместить тонкую обертку вокруг того, что уже было совершенно хорошим JSON). Я думаю, что нашел решение, и опубликую его завтра. Спасибо вам обоим!   -  person Eric    schedule 09.12.2016


Ответы (4)


Вы сериализуете его дважды (jsonData + вывод). Вы не можете этого сделать и ожидаете, что десериализуете его только один раз (выход).

Вы можете установить объект «данные» в своей динамике как настоящий объект С#, который будет работать. Или вы можете переименовать свое свойство в «jsonData»:

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

... так что это отражает то, что вы действительно делаете :).

person jvenema    schedule 08.12.2016
comment
Спасибо, jvenema, я ценю ответ. Моя проблема в том, что на самом деле это не объект С#. На самом деле это происходит прямо из SQL в виде строки JSON. У меня даже нет типа где-нибудь в моем среднем уровне, который приравнивается к нему. Я просто пытаюсь сделать так, чтобы веб-сервер был сквозным. Тем не менее, я думаю, что собрал решение с помощью собственного сериализатора на основе нескольких других потоков. Если получится, завтра выложу то, что получилось. - person Eric; 09.12.2016

Вот что у меня получилось....

// 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, приведенный выше код правильно обработает это.

person Eric    schedule 12.12.2016

Этого можно добиться, самостоятельно сформировав пакет JSON с помощью класса JsonWriter. от Ньютонсофт. Это будет выглядеть примерно так:

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;
}
person Michael Weinand    schedule 09.12.2016

Я бы подумал, что вам просто нужно сериализовать строку, возвращаемую из таблицы SQL, в объект, используя сериализатор JSON, например NewtonSoft.

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;
person Derek Van Cuyk    schedule 09.12.2016