Deserialising JSON using JsonSerializer.DeserializeAsync is not using my JsonConverter - c#

A server is returning a JSON string value which is a URL query string:
{
"parameters": "key1=value1&key2=value2"
}
I have a property set up to receive this, and convert it into a Dictionary as part of the deserialisation process:
Property with JsonConverter attribute:
[JsonConverter(typeof(QueryStringToDictionaryJsonConverter))]
public Dictionary<string, string> Parameters { get; set; }
Converter:
public class QueryStringToDictionaryJsonConverter : JsonConverter<Dictionary<string, string>> {
public override Dictionary<string, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
var queryString = reader.GetString();
if (string.IsNullOrEmpty(queryString)) return null;
return QueryHelpers.ParseQuery(queryString).ToDictionary(e => e.Key, e => string.Join(",", e.Value.ToArray()));
}
...
}
This should work.
But it's not even getting to my converter.
From what I can tell, JsonSerializer.DeserializeAsync<T>(myJson) is seeing that the type of property is a Dictionary, and so it tries to parse the value as such on its own, and fails (the resulting exception is an 'invalid cast' as it tries to GetEnumerable() etc). A breakpoint in my converter never even gets hit.
I can get it to work by making the property an object and then casting to a Dictionary later where it's used, but that's an ugly solution.
Is there a way to force JsonSerializer.DeserializeAsync<T>(myJson) to just use my converter, without it trying to be smart on its own?
(I'm using Microsoft's System.Text.Json in .NET Core 3)

OK, so this could be a bug in System.Text.Json.
Here's the workaround I'm currently using for anyone else needing a solution.
First, I set up two properties for deserialisation, using [JsonPropertyName] and [JsonIgnore]:
[JsonPropertyName("parameters"), JsonConverter(typeof(QueryStringToDictionaryJsonConverter))]
public object ParametersObject { get; set; }
[JsonIgnore]
public Dictionary<string, string> Parameters => ParametersObject as Dictionary<string, string>;
And then in the JsonConverter, I allow object as the type:
public override bool CanConvert(Type typeToConvert) {
if (typeToConvert == typeof(object)) return true;
return base.CanConvert(typeToConvert);
}
Consumers of my deserialised class just use the Parameters property, which will continue to work just fine if and when this bug is fixed and I change the class back to how I'd like it.

I would create a wrapper and create a converter for the wrapper.
[JsonConverter( typeof( QueryStringDictionaryConverter ) )]
class QueryStringDictionary : Dictionary<string,string> { }
class QueryStringDictionaryConverter : JsonConverter<QueryStringDictionary>
{
...
}
class MyClass
{
public QueryStringDictionary Parameters { get; set; }
}
Alternatively you could use JsonSerializerOptions
class MyOtherClass
{
public Dictionary<string,string> Parameters { get; set; }
}
MyOtherClass Deserialize( string json )
{
var options = new JsonSerializerOptions
{
Converters = { new QueryStringToDictionaryJsonConverter() }
};
return JsonSerializer.Deserialize<MyOtherClass>( json, options );
}
A potential problem with this approach is that the converter would be used on all Dictionary<string,string> properties, which may not be intended. It would work fine for the simple example in the original question.

Related

How can I serialize a property in an inherited ICollection<T> class?

I have a class that presented like
public class ItemCollection<T> : ICollection<T> {
public ItemCollection() {
Items = new List<T>();
}
public List<T> Items { get; set; }
...
}
Now it will be serialized into:
{
"Property": [{...}]
}
But I want the result is like:
{
"Property": {"Items": [{...}]}
}
Sorry for the missing information of this question.
I now stuck in serialization when using System.Text.Json.
In Newtonsoft.Json, I use [JsonObject] to annotate this class so it can serialization correctly into json with "Items": value, but I don't know how to serialize the Items property using System.Text.Json.
I have some classes inherited this class and the inheritances will be as properties in other classes.
Solution:
Thank you for every one that answered this question, I have found a solution to solve this. I create a ConverterFactory to resolve the needed types to create the converters. In the converter, I create new JsonObject and use Reflection to create the properties in the type, after this, I serialize the JsonObject so I can get the correct result.
Thank you for your question, but I did not found any difference between the serialization of Newtonsoft and System.Text.Json.
Perhabs you can provide an example in code to show us the problem directly.
With your provided information I got with following code example the same results
static void Main(string[] args)
{
ItemCollection<Person> list = new ItemCollection<Person> {new Person(){ FirstName = "FirstName", Name = "Name"}};
string jsonString = JsonSerializer.Serialize(list);
Console.WriteLine(jsonString);
string jsonString2 =Newtonsoft.Json.JsonConvert.SerializeObject(list);
Console.WriteLine(jsonString2);
Console.ReadLine();
}
[{"Name":"Name","FirstName":"FirstName"}]
There is no built-in attribute corresponding to Newtonsoft's JsonObjectAttribute that will force a collection to be serialized as a JSON object.1. And there is no public equivalent to IContractResolver that can be overridden to customize serialization metadata. Thus you will need to create a custom JsonConverter to serialize the properties of your ItemCollection<T>, such as the following:
[System.Text.Json.Serialization.JsonConverter(typeof(ItemCollectionJsonConverter))]
public partial class ItemCollection<T> : ICollection<T> {
internal ItemCollection(List<T> items) { // I added this for use by the converter
Items = items ?? throw new ArgumentNullException();
}
public ItemCollection() {
Items = new List<T>();
}
public List<T> Items { get; set; }
// Remainder omitted
}
public class ItemCollectionJsonConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => GetItemCollectionValueType(typeToConvert) != null;
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
=> (JsonConverter)Activator.CreateInstance(typeof(ItemCollectionJsonConverterInner<>).MakeGenericType(GetItemCollectionValueType(type)!))!;
static Type? GetItemCollectionValueType(Type type) =>
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ItemCollection<>)) ? type.GetGenericArguments()[0] : null;
class ItemCollectionJsonConverterInner<T> : JsonConverter<ItemCollection<T>>
{
class ItemCollectionDTO
{
public List<T>? Items { get; set; }
// Add other properties here
}
public override ItemCollection<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
// TODO: Decide whether to throw on null
new ItemCollection<T>((JsonSerializer.Deserialize<ItemCollectionDTO>(ref reader, options)?.Items) ?? throw new JsonException());
public override void Write(Utf8JsonWriter writer, ItemCollection<T> value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, new ItemCollectionDTO { Items = value.Items }, options);
}
}
Notes:
I added the converter directly to ItemCollection<T> via attributes, but you could add it to JsonSerializerOptions.Converters if you prefer.
Many serializers will not serialize collection properties. See e.g. XmlSerializer doesn't serialize everything in my class for another.
Adding properties to collections isn't really a recommended practice; see Why not inherit from List<T>? for a discussion why.
Demo fiddle here.
1 The list of all System.Text.Json serialization attributes is documented here.

Forcing System.Text.Json to fail when deserializing to a model with an enum property which is absent from the json string

I'd like deserialization to fail for the following model:
class ExampleModel
{
public ExampleEnum ExampleEnum { get; set; }
public string ExampleString { get; set; }
}
enum ExampleEnum
{
Value1,
Value2,
}
when the ExampleEnum's value is not explicitly specified, i.e.:
{
"ExampleString " : "abc"
}
It seems that it falls back to the default enum's value by, well, default:
string json = "{ \"exampleString\" : \"abc\" }";
var model = JsonSerializer.Deserialize<ExampleModel>(json);
Console.WriteLine(model.ExampleEnum); // outputs "Value1"
Is it possible to change the behavior?
Unfortunately I didn't find a easy way to do it. Unlike newtonsoft.json , system.text.json does not has this feature built in. The only way seems to be to write a custom converter.
Here is how your custom converter code should look like.
public class ExampleModelRequiredPropertyConverter : JsonConverter<ExampleModel>
{
public override ExampleModel Read(ref Utf8JsonReader reader,Type type,JsonSerializerOptions options)
{
ExampleModel model = JsonSerializer.Deserialize<ExampleModel>(ref reader);
if (model.ExampleEnum == default)
{
throw new JsonException("Required property not received in the JSON");
}
return model;
}
public override void Write(Utf8JsonWriter writer,ExampleModel model, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, model);
}
}
After defining the converter you need to add the converter to the JsonSerializerOptions.Converters collection so that it gets registered. You cannot register this converter as an attribute as that will result in Stack Overflow exception.
If you want to use the converter as an attribute these is an alternate approach defined here in the Microsoft docs. This doc also talks about the above mentioned approach.

Create custom JSON converter using Newtonsoft.JSON library to get additional data without overriding the CanRead and CanWrite flags

I am currently working on a Custom JSON converter to be used in a WebAPI project. The requirement is - I have a DTO object having some properties. The APIs can be consumed by multiple clients. Depending upon a client few of my DTO Entities might have some additional data apart from the properties already present in the DTO Model. I need to create a custom JSON converter to Serialize and Deserialize this data.
//DTO
class AbcDTO
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public List<AdditionalProperty> AdditionalData { get; set; }
}
//AdditionalProperty class
class AdditionalProperty
{
public string Name { get; set; }
public object Value { get; set; }
}
//Request JSON Body
{
"Prop1": "Val1",
"Prop2": "Val2",
"AdditionalProp3": "Val3",
"AdditionalProp4": "Val4"
}
//After Deserialization the object should be as below
AbcDTO dto = {
Prop1 = "Val1",
Prop2 = "Val2",
AdditionalData = [
{ Name = "AdditionalProp3", Value = "Val3" },
{ Name = "AdditionalProp4", Value = "Val4" }]
}
//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format
We don't want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we would need to keep the property as Dictionary<string, JToken> -- but we don't want to pass JToken to below layers.
Created a custom JSON converter -
class CustomJsonConverter : JsonConverter
{
bool _canWrite = true;
bool _canRead = true;
public override bool CanConvert(Type objectType)
{
return typeof(IEntity).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get
{
return _canWrite;
}
}
public override bool CanRead
{
get
{
return _canRead;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
PropertyInfo[] availablePropertyNames = objectType.GetProperties();
List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
IEntity obj;
_canRead = false;
obj = (IEntity)jObject.ToObject(objectType);
_canRead = true;
IEnumerable<JProperty> properties = jObject.Properties();
foreach (JProperty prop in properties)
{
if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
{
AdditionalProperties addProp = new AdditionalProperties
{
Name = prop.Name,
Value = prop.Value.ToObject<object>(),
};
additionalData.Add(addProp);
}
}
obj.AdditionalData = additionalData;
return obj;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IEntity obj = (IEntity)value;
List<AdditionalProperties> additionalData = obj.AdditionalData;
JObject jObj;
_canWrite = false;
jObj = (JObject)JToken.FromObject(obj);
_canWrite = true;
jObj.Remove("AdditionalData");
foreach (AdditionalProperties data in additionalData)
{
jObj.Add(data.Name, JToken.FromObject(data.Value));
}
jObj.WriteTo(writer);
}
}
WebAPI ContractResolver creates 1 JSON converter per Entity. Now the issue is _canRead and _canWrite are not thread-safe. Need to use them to use the base implementation provided by Newtonsoft. If we don't use them, the ToObject and FromObject method again calls the custom converter methods internally resulting in infinite recursion. Using them with logs, reduces performance. Is there any way we can create a custom converter using the base implementation of Newtonsoft.JSON serialization/deserialization without using canRead and canWrite flags?
I can also have reference type child properties - say Person contains Address. I want to capture additional data for both Parent and Child entities. The additional data will not contain data of reference type.
It's possible to disable the converter using a thread static variable or ThreadLocal<T> member, as shown in JSON.Net throws StackOverflowException when using JsonConvert or Generic method of modifying JSON before being returned to client. However, I'd like to suggest a simpler way of solving your problem.
You wrote, We dont want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we need to keep the property as Dictionary and we dont want to pass JToken to below layers. It is not necessary for the extension data dictionary to have values of type JToken. Values of type object are supported for extension data dictionaries, e.g.:
class AbcDTO
{
public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
[JsonExtensionData]
public Dictionary<string, object> AdditionalData { get; private set; }
}
When the extension data dictionary is of type Dictionary<string, object>, Json.NET will deserialize JSON primitive values to their equivalent .Net primitives -- string, bool, long and so on -- rather than to JValue objects. Only when encountering an additional property whose value is a JSON object or array will a JToken be added to the dictionary, in which case you can use the answers from How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? to convert the JToken to a conventional .Net type. (However, your question states that The additional data will not contain data of reference type, so this should not be necessary.)
Using [JsonExtensionData] in this manner completely avoids the need for a converter while also deserializing primitives as per your requirements, and thus seems much simpler than the original design shown in the question.
Sample .Net fiddle demonstrating that extension properties can be deserialized into AbcDTO and asserting that none of them are of type JToken.

Deserialize JSON object into C# class that wraps typed Dictionary field

I have a JSON object that I get from my REST API server that looks like this:
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
Normally I'd deserialize the object to a Dictionary of string to object, but I'd like to provide helper methods to get strongly typed versions of the values. I'm using JSON.NET to deserialize the JSON object. Here's what I've got so far:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, string> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, string>();
}
}
In order to wrap the dictionary within a class, it seems like I needed to create a custom JsonConverter, which currently looks like this:
public class SettingsJsonConverter : JsonConverter
{
#region implemented abstract members of JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject settingsObj = JObject.Load(reader);
Settings settings = new Settings();
foreach(KeyValuePair<string, JToken> entry in settingsObj)
{
settings.Entries.Add(entry.Key, entry.Value.ToString());
}
return settings;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Settings);
}
#endregion
}
Now obviously I could create methods in my Settings class like, GetDate that takes the value for a given key and creates a new DateTime object with the ISO string value. However, JSON.NET already provides facilities to do what I want to do, so I'd rather use its built-in deserialization for any values I iterate over, instead of doing it myself.
It seems as though the place to do this would be inside the foreach loop in SettingsJsonConverter#ReadJson, but I'm not quite sure how to get the Type (not a JTokenType, a standard C# Type) of a given JToken so I can pass it into JToken#ToObject(Type). Or, how can I iterate over a JsonReader to populate my internal Entries dictionary with already deserialized values, such that I just need to do simple casts in my helper methods in Settings? Here's how I'd like my Settings class to look:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, object> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, object>();
}
public DateTime GetDate(string key)
{
return (DateTime)this.Entries[key];
}
}
Am I even doing this correctly? It almost feels like there's some simple way I'm overlooking.
You can directly deserialize into a Dictionary<string, JToken> and use type casts as you need them so you don't need a Settings class or a custom converter. It doesn't look as nice as your solution, but the code is simpler:
var settings = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(json);
var date = (DateTime) settings["key"];
If you want to keep your Settings class, I'd suggest that you use the above line to deserialize the dictionary and create a new constructor in Settings with a dictionary parameter, so you end up with two constructors, one that creates a new dictionary, and one that uses a given dictionary to create the new one. (Don't re-use the parameter dictionary itself, you never know what the caller will do to the dictionary afterwards.)
public Settings(Dictionary<string, JToken> dict)
{
Entries = new Dictionary<string, JToken>(dict);
}
By the way, you can remove the JsonIgnore attribute because you have a custom converter class.
First of all we have to make a model for your corresponding json.
{
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
}
CSharp Model:
public class Settings
{
[JsonProperty("assets.last_updated_at")]
public string assets_last_updated_at { get; set; }
[JsonProperty("data.version")]
public int data_version { get; set; }
// Add other property here...
}
public class RootObject
{
public Settings settings { get; set; }
}
Deserialize Json string to Model :
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>("Put your Json String");
Hope this help.

Using JavaScriptConverter for DataContract serialization in C#

I am having problem to find some information about how to get a custom converter invoked for a given property when serializing a C# object to JSON (and vice-versa).
JSON looks like that:
{"ws_status": "success", "result": 32}
or
{"ws_status": "failure", "error_code": 32123}
I have my C# object:
[DataContract]
class WebServiceResult
{
[DataMember(Name = "ws_status", IsRequired = true)]
public Boolean Success { get; private set; }
[DataMember(Name = "error_code")]
public Int32 ErrorCode { get; private set; }
[DataMember(Name = "result")]
public Int32 Result { get; private set; }
}
What I am missing is how to get the JSON values "success" and "failure" converted to a boolean telling me if the WS was successful.
I have implemented a JavaScriptConverter but I don't know how to bind it to my C# object's property.
class JsonStatusConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(Boolean) })); }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
// Here, do we get a WebServiceResult or directly the WebServiceResult.Success
// object?
Boolean success = obj==null ? false : (Boolean) obj;
Dictionary<string, object> result = new Dictionary<string, object>();
result["ws_status"] = success ? "success" : "error";
return result;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (type == typeof(String))
{
String status = dictionary["ws_status"] as String;
if (status.Equals("success")) return true;
else return false;
}
return null;
}
}
Maybe I misunderstood the concept of JavaScriptConverter and it can only be implemented for the whole WebServiceResult object (which would be a pity because most of the properties are standard).
PS: I know I could simply get the status serialized directly to a string and have a helper method converting that to a boolean in the C# object, but I'd like to learn about converters as I will need them in some other objects.
JsonSerializer/JavaScriptSerializer and DataContractJsonSerializer are distinct entities. DataContractJsonSerializer does not support even the concept of JavaScriptConverters, and JavaScriptSerializer in turn does not honor the DataContract programming model. In addition, with DataContractJsonSerializer, there is no way to do what you want to do here, at all, because boolean is a fundamental primitive in its serialization model, and in its world, extensibility points for primitives are severely curtailed, if not non-existent.
I think your best bet here (sadly) is to ditch this entire model altogether and use Json .NET

Categories