How can I serialize and deserialize a type with a string member that contains raw JSON, without escaping the JSON in the process
Your question is, How can I serialize and deserialize a type with a string
member that contains "raw" JSON, without escaping the JSON in the process?
This can be done via a custom JsonConverter
that reads and writes raw JSON using JsonWriter.WriteRawValue()
and JRaw.Create()
:
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);
}
}
Then apply it to your type as follows:
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
// **Note** : I'm not using List<Address> data type for Address, instead of I want list of address in JSON string
[JsonConverter(typeof(RawConverter))]
public string Address { get; set; }
}
public class RootObject
{
public List<Employee> Employees { get; set; }
}
Sample fiddle.
Note that the raw JSON string must represent valid JSON. If it does not, then the JSON created will be unreadable. If you want to guarantee that the JSON literal is valid, you can keep the JSON in a parsed state internally:
public class Employee
{
public int EmpId { get; set; }
public string EmpName { get; set; }
[JsonProperty("Address")]
JToken AddressToken { get; set; }
[JsonIgnore]
public string Address
{
get
{
if (AddressToken == null)
return null;
return AddressToken.ToString(Formatting.Indented); // Or Formatting.None if you prefer
}
set
{
if (value == null)
AddressToken = null;
else
// Throw an exception if value is not valid JSON.
AddressToken = JToken.Parse(value);
}
}
}
A converter is not needed for this implementation.
Deserialize JSON into any object
I have acheived something similar using Newtonsoft
Your route should take the body in as a generic object and it can then be deserialized into any object you'd like:
/*Using this method, the controller will automatically handle
validating proper Json format*/
[HttpPost]
public async Task<IActionResult> Post([FromBody] object Body)
{
/*here you will send the generic object to a service which will deserialize.
the object into an expected model.*/
customService.HandlePost(Body);
}
Now create an object with any expected fields you would get from the body. (Json2csharp.com is extremely useful!)
public class MessageBody
{
public string Token { get; set; }
public string Name { get; set; }
}
Inside your service you can handle the object like this:
using Newtonsoft.Json
using Models.MessageBody
public class customService()
{
public void HandlePost(object body)
{
var DeserializedBody = JsonConvert.DeserializeObject<MessageBody>(body);
//Any Values that were not assigned will be null in the deserialized object
if(DeserializedBody.Name !== null)
{
//do something
}
}
}
This is obviously a very bare bones implementation, error handling will be important to catch any invalid data. Instead of using one object and null fields to get the data you need, I would recommend adding a "subject" route variable (string) that you can use to determine which object to deserialize the body into.
post *api/MessageBody/{Subject}
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.
Storing original JSON string in deserialised JSON.NET objects
I'm leaving the question open in the hope that someone comes up with a better answer but I've temporarily used the following solution to resolve my problem.
public static class MessageExtensions
{
public static T Deserialize<T>(this string message) where T : Message
{
T instance = Activator.CreateInstance<T>();
instance.Original = message;
JsonConvert.PopulateObject(message, instance);
return instance;
}
}
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.
Deserializing a complex type but keep selected properties as serialized
The key here is use a different class for persistence, the class that will be deserialized from JSON, let's name it PersistablePerson
.
I can think of two approaches:
Approach 1 - using custom converter
Based on another answer on SO suggested by @dbc, create a custom converter:
public class RawConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.TokenType != JsonToken.Null
? JRaw.Create(reader).ToString()
: null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue((string)value);
}
}
The persistable class would look like this:
public class PersistablePerson
{
public string name {get; set;}
public int department {get; set;}
[JsonConverter(typeof(RawConverter))]
public string addresses {get; set;}
[JsonConverter(typeof(RawConverter))]
public string skill {get; set;}
}
Approach 2 - using doubled properties
In the persistable class, double the "complex" properties so that every such property is represented by two:
- a string property: included in persistence, ignored by JSON serializer
- a
JToken
property: handled by JSON serializer, excluded from persistence
The JToken
property setter and getter are wrappers that read and write the string property.
public class PersistablePerson
{
public string name {get; set;}
public int department {get; set;}
[JsonIgnore] // exclude from JSON serialization, include in persistence
public string addresses {get; set;}
[IgnoreProperty] // exclude from persistence
[JsonProperty("addresses")] // include in JSON serilization
public JToken addressesJson
{
get { return addresses != null ? JToken.Parse(addresses) : null; }
set { addresses = value.ToString(); }
}
[JsonIgnore] // exclude from JSON serialization, include in persistence
public string skill {get; set;}
[IgnoreProperty] // exclude from persistence
[JsonProperty("skill")] // include in JSON serilization
public JToken skillJson
{
get { return skill != null ? JToken.Parse(skill) : null; }
set { skill = value.ToString(); }
}
}
Related Topics
System.Text.JSON API Is There Something Like Icontractresolver
Why Switch for Enum Accepts Implicit Conversion to 0 But No for Any Other Integer
Calling Function from Generated Button in Blazor
Databinding to List - See Changes of Data Source in Listbox, Combobox
Null or Default Comparison of Generic Argument in C#
Why Does Calling a Method in My Derived Class Call the Base Class Method
How to Fix "Referenced Assembly Does Not Have a Strong Name" Error
ASP.NET MVC Ambiguous Action Methods
C# - Approach for Saving User Settings in a Wpf Application
ASP.NET Core 2 API Post Objects Are Null
How to Use Class Fields with System.Text.JSON.JSONserializer
How to Pass an Object from Form1 to Form2 and Back to Form1
Sorting a List Using Lambda/Linq to Objects
Logging Request/Response Messages When Using Httpclient