Efficiently get full json string in JsonConverter.ReadJson()
Solution 1:
ReadJson()
must fully parse the JSON being read so that the JSON is confirmed to be well-formed and the JsonReader
is correctly positioned at the end of the current value upon exit. However, it is not necessary to load the entire JSON into an intermediate JObject
hierarchy simply to re-convert it to a JSON string. Instead, you may be able to get better performance by using JRaw.Create()
:
var json = JRaw.Create(reader).ToString();
As can be seen in the reference source, this method streams directly from the incoming JsonReader
to a StringWriter
- without loading into an intermediate JToken
hierarchy and re-serializing - by using JsonWriter.WriteToken(JsonReader)
:
public static JRaw Create(JsonReader reader) { using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture)) using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) { jsonWriter.WriteToken(reader); return new JRaw(sw.ToString()); } }
The resulting JRaw
simply encapsulates that string in its Value
. (Of course, there is no guarantee that the resulting JSON represents an object, only that it represents well-formed JSON.)
Note that JsonTextReader
will automatically recognize and parse dates and times in common formats as DateTime
objects, and also parse floating point values as double
. If you need the "most literal" JSON string you may want to suppress DateTime
recognition and/or parse floating point values as decimal
. The following extension method, modeled on JRaw.Create()
, does the job:
public static string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = null)
{
// If you would prefer a null JSON value to return an empty string, remove this line:
if (reader.TokenType == JsonToken.Null)
return null;
var oldDateParseHandling = reader.DateParseHandling;
var oldFloatParseHandling = reader.FloatParseHandling;
try
{
if (dateParseHandling != null)
reader.DateParseHandling = dateParseHandling.Value;
if (floatParseHandling != null)
reader.FloatParseHandling = floatParseHandling.Value;
using (var sw = new StringWriter(CultureInfo.InvariantCulture))
using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
{
jsonWriter.WriteToken(reader);
return sw.ToString();
}
}
finally
{
reader.DateParseHandling = oldDateParseHandling;
reader.FloatParseHandling = oldFloatParseHandling;
}
}
And then call it like, e.g.:
var json = reader.ReadOuterJson(dateParseHandling: DateParseHandling.None);
For details on why this may be necessary, see:
Json.NET interprets and modifies ISO dates when deserializing to JObject #862.
JObject.Parse modifies end of floating point values.