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
implementsIDictionary<String,IJsonValue>
andJsonArray
implements severalIEnumerable
interfaces, so Json.NET will know how to serialize these types. it will not, however, know to deserialize them by calling the appropriate staticParse()
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 deserializeJsonValue
.Alternatively, you could consider replacing
JsonObject
with Json.NET'sJObject
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 astring
in your data model, and mark the property with[JsonConverter(typeof(RawConverter))]
whereRawConverter
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 withJsonConstructorAttribute
:[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
How to I Apply Filter While Paginating in ASP.NET MVC and Entity Framework
Static Property Using Inotifypropertychanged. C#
How to Set Attributes Values Using Reflection
Login Using Google Oauth 2.0 with C#
How to Make Chrome Headless After I Login Manually
Looking for a Command Line Argument Parser for .Net
Asynchronously Sending Emails in C#
Password Encryption/Decryption Code in .Net
How to Solve Disturbance in My Bot in C#
Azure Shared Access Signature - Signature Did Not Match
How to Translate Cultureinfo Language Names
How to Clear Event Subscriptions in C#
Dbset.Attach(Entity) VS Dbcontext.Entry(Entity).State = Entitystate.Modified
Entity Framework Many to Many Through Containing Object
Best Practices for Serializing Objects to a Custom String Format for Use in an Output File