How to Serialize and Deserialize a Type with a String Member That Contains "Raw" JSON, Without Escaping the JSON in the Process

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 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.

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



Leave a reply



Submit