2016-12-08 2 views
0

Il y a longtemps, je mis une norme de codage pour mon application que toutes les actions de retour JSON aurait leurs résultats mis dans un objet wrapper de haut niveau:Personnalisation sérialisation JSON en sortie d'action MVC

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

qui a bien fonctionné , et m'a fourni un bon standard de normalisation de code. Aujourd'hui, cependant, je me suis rendu compte que mon code côté serveur suppose qu'il doit toujours effectuer la sérialisation complète de ce qui est renvoyé. Maintenant, je voudrais sérialiser l'un de ces gars où la charge "data" est déjà une chaîne JSON bien formée.

Ceci est le schéma général qui avait travaillé:

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; 

Là où le bât est que l'élément « de données » sera reçue comme une chaîne quand il est au navigateur, et non pas comme le JSON bon objet (ou tableau dans l'exemple ci-dessus) il devrait être.

Y a-t-il un moyen de décorer une propriété avec un attribut qui dit «serialize as raw», ou suis-je dans le domaine de l'écriture d'un sérialiseur JSON personnalisé pour que cela fonctionne?

+1

Vous devez utiliser 'json.parse'. Lire tous les détails ici https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse –

+1

Eh bien, techniquement, vous brisez votre propre contrat et standard. Auparavant, vous attendiez un * objet *, maintenant vous lui passez une chaîne. Il me semble que c'est une mauvaise façon de faire les choses, mais vous * pouvez * écrire 'data = JsonSerializer.Deserialize (jsonData)' s'il n'y a aucun moyen d'éviter cette chaîne. Notez cependant que vous ne le sérialiserez que pour le désérialiser ultérieurement. – Rob

+0

json.parse se trouve à la fin du navigateur.J'essaie de manipuler la sérialisation du côté serveur. Rob, vous êtes exactement sur le problème .. Je ne veux pas le déssérialiser, juste pour ensuite le REserialize immédiatement (et vraiment juste pour mettre un emballage mince autour de ce qui était déjà parfaitement bon JSON). Je pense que j'ai rassemblé une solution, et posterai cela demain. Merci à vous deux! – Eric

Répondre

0

Voici ce que j'ai fini avec ....

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

Avec cela, je ne fais aucune analyse JSON sur le serveur. Ma chaîne sort de la base de données et va directement au client sans que MVC ne la touche.

EDIT: La version mise à jour prend désormais en charge la sérialisation des objets possédant des propriétés individuelles dans leur hiérarchie, de type JsonStringWrapper. Ceci est utile dans mon scénario pour supporter un modèle "hybride". Si l'objet A a une propriété B qui est l'une de mes chaînes JSON pré-cuites, le code ci-dessus traitera correctement cela.

1

Vous le sérialisez deux fois (sortie jsonData +). Vous ne pouvez pas faire cela et espérer ne le désérialiser qu'une seule fois (sortie).

Vous pouvez définir l'objet "data" dans votre dynamique comme étant le vrai objet C#, cela fonctionnerait. Ou vous pouvez renommer votre propriété à « jsonData »:

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

... il reflète ce que vous faites vraiment :).

+0

Merci, jvenema, j'apprécie la réponse. Mon problème est que ce n'est pas vraiment un objet C#. Il vient en fait directement de SQL en tant que chaîne JSON. Je n'ai même pas de type dans mon niveau intermédiaire qui soit équivalent. J'essaye juste d'avoir le serveur Web être un pass-through. Cela dit - je pense que j'ai rassemblé une solution avec un sérialiseur personnalisé, basé sur un couple d'autres threads. Si cela fonctionne, je posterai ce que j'ai trouvé demain. – Eric

0

Vous pouvez accomplir ceci en formant le paquet JSON vous-même en utilisant la classe JsonWriter de Newtonsoft. Il ressemblerait à quelque chose comme ceci:

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; 
} 
0

Je pense que vous avez juste besoin de sérialisation la chaîne étant de retour de la table SQL dans un objet, en utilisant un sérialiseur JSON, comme 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;