6

In order to increase performance, I have cached the result of a larger operation as JSON in a table - together with a key column to determine which row(s) to return. So the data looks some like this:

Id   Json
---- ---------
1    {"property": "data", "...": "..."}
2    {"property": "data", "...": "..."}

Hence, my retrieved object has the properties int .Id and string .Json. When returning such an object with the Id, I first need to deserialize the JSON - so that it gets properly re-serialized. If I don't deserialize it first, I end up with a quoted string, i.e. my return object would look like this

{
  "id": 1,
  "json": "{\"property\": \"data\", ...
}

Instead, I need:

{
  "id": 1,
  "json": {
      "property": "data", 
      ...
  }
}

Is there a way to "tell" the Json.Net serializer to output the .Json property directly without serializing - while serializing the other properties?

Peter Albert
  • 15,882
  • 4
  • 59
  • 83
  • so if i understand your output is {"id":1,"Json":{"data1":"value","data2":"value","data3":"value"}} and instead you want {"id":1,"Json":"\"data1\":\"value\",\"data2\":\"value\",\"data3\":\"value\""}? – VeNoMiS Jul 03 '15 at 09:29
  • @VeNoMiS: See the updated question – Peter Albert Jul 03 '15 at 09:57
  • I think you need to write your own JsonConverter for that type. – Andrei Tătar Jul 03 '15 at 10:02
  • using 2 class (container and item) with container that has 2 prop (int id and Item json) and item with your prop. Item m = JsonConvert.DeserializeObject(row.json) should do the trick – VeNoMiS Jul 03 '15 at 10:53
  • Are you sure it's valid JSON? Is has to be valid if you don't want Json.NET to escape it. – dbc Aug 30 '15 at 05:29
  • Also, what do you mean by "mess with its contents"? Do you just it to not get escaped (so it becomes part of the output JSON) or do you want the indenting preserved? – dbc Aug 30 '15 at 05:40

3 Answers3

9

Assuming you have a structure like this for serializing:

public class Record
{
    [JsonProperty("id")]
    public int Id
    {
        get;
        set;
    }

    [JsonProperty("json")]
    [JsonConverter(typeof(SpecialJsonConverter))]
    public string Json
    {
        get;
        set;
    }
}

And you use code like this for serialization:

    var data = new []
    { 
        new Record() { Id=1, Json = "{\"property\":\"data\"}" }, 
        new Record() { Id=2, Json = "{\"property\":\"data2\", \"property2\":[1, 2, 3]}" }
    };

    var serialized = JsonConvert.SerializeObject(data);
    Console.WriteLine(serialized);

All you need is to write a proper converter for the Json property. Luckily there is a method WriteToken in the JsonWriter class that could serve our needs:

public sealed class SpecialJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var reader = new JsonTextReader(new StringReader(value.ToString()));
        writer.WriteToken(reader);
    }
}
Alex Zhevzhik
  • 3,267
  • 16
  • 18
  • 2
    I would like to add that you can also just write the following inside WriteJson: `writer.WriteToken(JsonToken.Raw, value);` – Shahin Dohan Aug 08 '16 at 09:56
  • 1
    `writer.WriteRawValue((string)value);` as shown [here](https://stackoverflow.com/a/32293568/3744182) would be simpler. – dbc May 05 '19 at 20:17
8

You could make a JsonConverter to write the raw value of the string property to the output without changing it. You take responsibility for ensuring the string has valid JSON or else the resulting output will not be valid JSON either.

Here is what the converter might look like:

class RawJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(string));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // write value directly to output; assumes string is already JSON
        writer.WriteRawValue((string)value);  
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // convert parsed JSON back to string
        return JToken.Load(reader).ToString(Formatting.None);  
    }
}

To use it, mark your JSON property with a [JsonConverter] attribute like this:

class Foo
{
    ...
    [JsonConverter(typeof(RawJsonConverter))]
    public string YourJsonProperty { get; set; }
    ...
}

Here is a demo: https://dotnetfiddle.net/BsTLO8

Brian Rogers
  • 110,187
  • 27
  • 262
  • 261
2

Based on answer by Alex and comment by Shahin, I improved the converter a bit, and also implemented the reader to work also the other way (parse back from JToken to the string property):

public sealed class RawDataJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var tokenReader = reader as JTokenReader;
        var data = tokenReader.CurrentToken.ToString(Formatting.None);
        return data;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteToken(JsonToken.Raw, value);
    }
}
awe
  • 20,650
  • 5
  • 76
  • 84