14

In an ASP.NET Web API application, some of the models I'm working with contain a chunk of ad-hoc JSON that is useful only on the client side. On the server it simply goes in and out of a relational database as a string. Performance is key, and it seems pointless to process the JSON string server side at all.

So in C#, imagine an object like this:

new Person
{
    FirstName = "John",
    LastName = "Smith",
    Json = "{ \"Age\": 30 }"
};

By default, Json.NET will serialize this object like this:

{
    "FirstName": "John",
    "LastName": "Smith",
    "Json": "{ \"Age\": 30 }"
}

I'd like to be able to instruct Json.NET to assume that the Json property is already a serialized representation, thus it shouldn't re-serialize, and the resulting JSON should look like this:

{
    "FirstName": "John",
    "LastName": "Smith",
    "Json": {
        "Age": 30
    }
}

Ideally this works in both directions, i.e. when POSTing the JSON representation it will automatically deserialize to the C# representation above.

What is the best mechanism to achieve this with Json.NET? Do I need a custom JsonConverter? Is there a simpler attribute-based mechanism? Efficiency matters; the whole point is to skip the serialization overhead, which could be a bit of a micro-optimization, but for argument's sake let's assume it's not. (There will potentially be big lists with bulky Json properties being returned.)

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
  • If your property `Json` is a string, then it should be serialized as a string. I'm not entirely sure what the problem is. Just serialize it as a string, store it in your database as a string and deserialize it back as a string. – Matt Burland Sep 29 '15 at 17:35
  • Maybe I didn't explain it well. I added an extra JSON sample to highlight the difference between what happens by default and what I'm looking to achieve. – Todd Menier Sep 29 '15 at 17:44
  • 1
    If your server side code is getting the JSON string from somewhere that you can control, you'd be far better off fixing that part of the problem than trying to come up with a resolution for this. However, if it is coming from something that you don't have access to, my first attempt would be to deserialize it when you get it from whatever source you retrieve it from. That could be done with a custom deserializer. There just isn't enough information to come up with the best solution for you. – krillgar Sep 29 '15 at 17:48
  • 1
    I think the only way is with a custom converter, you need to take that JSON string, deserialize it to a JSON object, and then add it to the serialized value if you want it to serialize "inline" JSON. – Ron Beyer Sep 29 '15 at 17:50

3 Answers3

25

If you are able to change the type of the Json property on Person from string to JRaw then you will get the desired result.

public class Person
{
    public string FirstName { get; set;}
    public string LastName { get; set;}        
    public JRaw Json  { get; set;}
} 

Alternatively, you could keep the string property, and add a JRaw property to be used as a proxy for serialization:

public class Person
{
    public string FirstName { get; set;}
    public string LastName { get; set;}
    [JsonIgnore]
    public string Json { get; set; }

    [JsonProperty("Json")]
    private JRaw MyJson
    {
        get { return new JRaw(this.Json); }
        set { this.Json = value.ToString(); }
    }        
}

Either way, both serialization and deserialization will work as you have requested.

Mike Hixson
  • 5,071
  • 1
  • 19
  • 24
  • 2
    I didn't know about `JRaw` and looks to be the best fit for what I'm trying to solve. Thanks. – Todd Menier Sep 29 '15 at 22:54
  • FWIW, because of this answer, I think this question should be the one that the other "already asked" questions reference. – HeyZiko Jan 02 '17 at 22:24
1

I'm not sure this is exactly a useful thing to do, but you could create a custom converter like this:

public class StringToJsonConverter : JsonConverter
{
    public override bool CanConvert(Type t)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var o = JsonConvert.DeserializeObject(value.ToString());
        serializer.Serialize(writer,o);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var o = serializer.Deserialize(reader);
        return JsonConvert.SerializeObject(o);
    }
}

Now if you decorate you Json property with [JsonConverter(typeof(StringToJsonConverter))], you can do this:

var obj = new Person
{
    FirstName = "John", 
    LastName = "Smith", 
    Json = "{ \"Age\": 30 }"
};

var s = JsonConvert.SerializeObject(obj);

And get this:

{"FirstName":"John","LastName":"Smith","Json":{"Age":30}}

Here's a fiddle

One note however in my fiddle, I'm round tripping the serialization and deserialization, but the values in the Json property aren't exactly the same. Why? Because the extra spaces around your curly braces have been stripped out in the process. Of course, they aren't (or shouldn't be) significant.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
  • Down voter care to explain? It achieves exactly what the OP asked for. – Matt Burland Sep 29 '15 at 17:56
  • I can tell you it wasn't me :) – Todd Menier Sep 29 '15 at 17:58
  • That did work (+1). But to your point, I'm beginning to question the usefulness. Since the property only needs to exist as a string on the server, from raw request to the database and vice-versa, I was hoping for some solution that doesn't involve serializing/deserializing at all. But as a property of a parent object I'm starting to think that's either not possible or not worth the effort. – Todd Menier Sep 29 '15 at 18:17
0

I am not sure there is a way to skip the piece of serialization. Here is an option to serialize and deserialize in a simple way.

public class Person
{
    public string FirstName = "John";
    public string LastName = "Smith";
    [JsonIgnore]
    public string Json = "{ \"Age\": 30 }";
    public JObject JsonP { get { return JsonConvert.DeserializeObject<JObject>(Json); } }
}
XtremeBytes
  • 1,469
  • 8
  • 12