Efficiently Get Full JSON String in JSONconverter.Readjson()

Efficiently get full json string in JsonConverter.ReadJson()

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.

In Json.Net's JsonConverter ReadJson(), why is reader..Value null?

I think you have a misconception about how JsonConverters work. The reader does not give you the raw JSON. Instead it allows you to step through the tokens in the JSON one by one.

reader.Value is null because your converter is handling an object, not a string, and at the point your converter is called, the reader is positioned on the StartObject token. You need to call reader.Read() in a loop to advance through the object to get the property names and values until you reach the EndObject token.

Here is a fiddle that demonstrates that process, although it doesn't actually populate the Polygon. https://dotnetfiddle.net/MQE6N5

If you really want the JSON string inside the converter, you could load a JObject from the reader, and then call its ToString() method:

public override Polygon ReadJson(JsonReader reader, Type objectType, Polygon existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string json = jo.ToString(Formatting.None);

Polygon poly = ConvertJsonToPolygon(json); // your conversion method

return poly;
}

A better solution IMO is to use the facilities of the JObject to actually do the conversion. You can easily pick values out of the JObject and populate your Polygon class from it:

public override Polygon ReadJson(JsonReader reader, Type objectType, Polygon existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Polygon poly = new Polygon();
poly.type = (string)jo["type"];
poly.coordinates = jo["coordinates"].ToObject<double[][][]>(serializer);
return poly;
}

Fiddle: https://dotnetfiddle.net/OFSP1h

Reading sub-objects from json as strings with Json.NET (C#)

If I understand you correctly, what you are looking for is persisting content of JsonReader as string, and that's what JRaw does.

class ObjectJsonWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;

public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// This creates a raw JSON string without parsing it.
var raw = JRaw.Create(reader);
var obj = new ObjectJsonWrapper();
obj.Json = (string)raw.Value;
return obj;
}

public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

To use it:

public class MyObject
{
public ObjectJsonWrapper LargeObject { get; set; }
}

[JsonConverter(typeof(ObjectJsonWrapperConverter))]
public class ObjectJsonWrapper
{
public string Json { get; set; }
}

var json = @"
{
""largeObject"": {
""value"": ""some value""
}
}
";

var obj = JsonConvert.DeserializeObject<MyObject>(json);

How to deserialize json inside a json to a string property

One workaround is to add a field to Unit called CostCentersString in which the CostCenters list is re-serialized:

In Unit class definition:

...

public List<dynamic> CostCenters { get; set; }

public String CostCentersString { get; set; }

Then use:

List<Unit> units = JsonConvert.DeserializeObject<List<Unit>>(jsonString);

foreach (Unit u in units)
{
u.CostCentersString = JsonConvert.SerializeObject(u.CostCenters);
}

C# How to save part of nested JSON into object property, but not deserialized?

As @dbc commented you can create a custom JsonConverter for the DataJson property, but you also should do something with your another property which is mapped from the data JSON field - Data of DataDTO type. I can propose the following solution:

1. Custom JSON Converter (I took this from @dbc's answer)

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

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var raw = JRaw.Create(reader);
return raw.ToString();
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}

2. Decorate your DataJson property with the JsonConverter attribute and remove JsonPropertyAttribute for the Data property

Note that if you don't remove the JsonPropertyAttribute then it won't work, since you have two properties which are mapped from the same JSON field, and as I know this is not supported by Json.NET by default.

public class BaseMessage
{
[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("created_at")]
public long CreatedAt { get; set; }

public DataDTO Data { get; set; }

[JsonProperty("data")]
[JsonConverter(typeof(RawConverter))]
public string DataJson {get; set;}
}

3. Update your BaseMessage class so this calculates the value of the Data property from DataJson

public class BaseMessage
{
private DataDTO data;

[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("created_at")]
public long CreatedAt { get; set; }

public DataDTO Data
{
if (data == null)
{
data = JsonConvert.DeserializeObject<DataDTO>(DataJson);
}
return data;
}

[JsonProperty("data")]
[JsonConverter(typeof(RawConverter))]
public string DataJson {get; set;}
}

Note that I believe this is not the best solution and sure that there are other much better alternatives, but it might be feasible in your case.

How to serialize class to JSON, where property is of type Windows.Data.Json.JsonObject?

Json.NET does not have any support for the types in the Windows.Data.Json namespace, so you will need to create a custom JsonConverter for JsonObject as well as JsonArray and JsonValue if you ever use those classes directly. The following should do the job:

public class WindowsDataJsonObjectConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonObject>
{
public override JsonObject ReadJson(JsonReader reader, Type objectType, JsonObject existingValue, bool hasExistingValue, JsonSerializer serializer) =>
JsonObject.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public class WindowsDataJsonArrayConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonArray>
{
public override JsonArray ReadJson(JsonReader reader, Type objectType, JsonArray existingValue, bool hasExistingValue, JsonSerializer serializer) =>
JsonArray.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public class WindowsDataJsonValueConverter : WindowsDataJsonConverterBase<Windows.Data.Json.JsonValue>
{
public override JsonValue ReadJson(JsonReader reader, Type objectType, JsonValue existingValue, bool hasExistingValue, JsonSerializer serializer) =>
JsonValue.Parse(reader.ReadOuterJson(dateParseHandling: DateParseHandling.None));
}

public abstract class WindowsDataJsonConverterBase<TJsonValue> : JsonConverter<TJsonValue> where TJsonValue : IJsonValue
{
public override void WriteJson(JsonWriter writer, TJsonValue value, JsonSerializer serializer) =>
writer.WriteRawValue(value.Stringify());
}

public static partial class JsonExtensions
{
// Taken from this answer https://stackoverflow.com/a/56945050/3744182
// To https://stackoverflow.com/questions/56944160/efficiently-get-full-json-string-in-jsonconverter-readjson
public static string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = 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;
}
}
}

Notes:

  • JsonObject implements IDictionary<String,IJsonValue> and JsonArray implements several IEnumerable interfaces, so Json.NET will know how to serialize these types. it will not, however, know to deserialize them by calling the appropriate static Parse() methods.

  • There is no standard interface to indicate that a c# type should be serialized to JSON using its "raw" ToString() value, so Json.NET has no way to know how to serialize or deserialize JsonValue.

  • Alternatively, you could consider replacing JsonObject with Json.NET's JObject in your model. If you do, Json.NET will be able to serialize it without any conversion.

    As a second alternative, you could leave _captureResultData as a string in your data model, and mark the property with [JsonConverter(typeof(RawConverter))] where RawConverter comes from this answer to How can I serialize and deserialize a type with a string member that contains "raw" JSON, without escaping the JSON in the process.

  • Since ImageDataModel is immutable, if you want to deserialize it with Json.NET you will need to create a compatible constructor and mark it with JsonConstructorAttribute:

     [JsonConstructor]
    ImageDataModel(JsonObject captureResultData, LightStageCommand lightStageCommand, SmartphoneCommand smartphoneCommand, string timeStamp)
    {
    this.CaptureResultData = captureResultData;
    this.SmartphoneCommand = smartphoneCommand;
    this.LightStageCommand = lightStageCommand;
    this.TimeStamp = timeStamp;
    }

    Json.NET will match the JSON properties to constructor arguments using a case-invariant name match.

Mockup fiddle here.

How can I deserialize JSON to a simple Dictionarystring,string in ASP.NET?

Json.NET does this...

string json = @"{""key1"":""value1"",""key2"":""value2""}";

var values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);

More examples: Serializing Collections with Json.NET



Related Topics



Leave a reply



Submit