1

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?

3
  • 1
    You have to use json.parse. Read full details here developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Commented Dec 8, 2016 at 23:05
  • 1
    Well, technically you're breaking your own contract and standard. Previously you're expecting an object, now you're passing it a string. It looks to me like it's a bad way to go about things, but you could write data = JsonSerializer.Deserialize(jsonData) if there's no way to avoid this string. Do note, though, that you'll be serializing it only to have it deserialized later. Commented Dec 8, 2016 at 23:05
  • json.parse is on the browser end. I'm trying to manipulate the serialization on the server side. Rob, you're exactly onto the problem.. I don't want to DEserialize it, just to then immediately REserialize it (and really just to put a thin wrapper around what was already perfectly good JSON). I think I've pulled together a solution, though, and will post that tomorrow. Thank you both! Commented Dec 9, 2016 at 1:06

4 Answers 4

1

You're serializing it twice (jsonData + output). You can't do that and expect to only deserialize it once (output).

You could set the "data" object in your dynamic to be the real c# object, that would work. Or you can re-name your property to "jsonData":

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

...so it reflects what you're really doing :).

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, jvenema, I appreciate the response. My trouble is that this isn't really a C# object. It's actually coming straight from SQL as a JSON string. I don't even have a type anywhere in my middle-tier that equates to it. I'm just trying to have the web server be a pass-through. That said - I think I've pulled together a solution with a custom serializer, based on a couple of other threads. If it works, I'll post what I came up with tomorrow.
1

I would think that you just need to serialize the string being returned from the SQL table into an object, using a JSON serializer, like 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;

Comments

1

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.

Comments

0

You could accomplish this by forming the JSON package yourself using the JsonWriter class from Newtonsoft. It would look something like this:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.