In the following scenario, how do I get CrazyItemConverter
to carry on as usual when it encounters a JSON property that exists in the type I'm deserializing to?
I have some JSON that looks like this:
{
"Item":{
"Name":"Apple",
"Id":null,
"Size":5,
"Quality":2
}
}
The JSON gets deserialized into a class that looks a whole lot like this:
[JsonConverter(typeof(CrazyItemConverter))]
public class Item
{
[JsonConverter(typeof(CrazyStringConverter))]
public string Name { get; set; }
public Guid? Id { get; set; }
[JsonIgnore]
public Dictionary<string, object> CustomFields
{
get
{
if (_customFields == null)
_customFields = new Dictionary<string, object>();
return _customFields;
}
}
...
}
CrazyItemConverter
populates the values of the known properties and puts the unknown properties in CustomFields. The ReadJson
in it looks like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var outputObject = Create(objectType);
var objProps = objectType.GetProperties().Select(p => p.Name).ToArray();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
string propertyName = reader.Value.ToString();
if (reader.Read())
{
if (objProps.Contains(propertyName))
{
// No idea :(
// serializer.Populate(reader, outputObject);
}
else
{
outputObject.AddProperty(propertyName, reader.Value);
}
}
}
}
return outputObject;
}
During deserialization, when CrazyItemConverter
encounters a known property, I want it to act as it normally would. Meaning, respecting the [JsonConverter(typeof(CrazyStringConverter))]
for Name
.
I was using the code below to set the known properties but, it throws exceptions on nullables and doesn't respect my other JsonConverters.
PropertyInfo pi = outputObject.GetType().GetProperty(readerValue, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var convertedValue = Convert.ChangeType(reader.Value, pi.PropertyType);
pi.SetValue(outputObject, convertedValue, null);
Any ideas?
UPDATE: I've learned that serializer.Populate(reader, outputObject);
is how to deserialize the whole thing but it doesn't seem to work if you want default functionality on a property-by-property basis.
Implement deserialization by doing the following steps in the Read () method: Parse JSON into a JsonDocument. Get the properties needed to call the parameterized constructor. Create the object with the parameterized constructor. Set the rest of the properties.
When you use System.Text.Json indirectly in an ASP.NET Core app, some default behaviors are different. For more information, see Web defaults for JsonSerializerOptions. By default, all public properties are serialized. You can specify properties to ignore. By default, JSON is minified. You can pretty-print the JSON.
By default, all public properties are serialized. You can specify properties to ignore. The default encoder escapes non-ASCII characters, HTML-sensitive characters within the ASCII-range, and characters that must be escaped according to the RFC 8259 JSON spec. By default, JSON is minified.
CustomCreationConverter. The CustomCreationConverter<T> is a JsonConverter that provides a way to customize how an object is created during JSON deserialization. Once the object has been created it will then have values populated onto it by the serializer.
If I'm understanding correctly, your CrazyItemConverter
exists so that you can deserialize known properties in the JSON to strongly-typed properties, while still preserving "extra" fields that may be in the JSON into dictionary.
It turns out that Json.Net already has this feature built in (since 5.0 release 5), so you don't need a crazy converter. Instead, you just need to mark your dictionary with the [JsonExtensionData]
attribute. (See the author's blog for more information.)
So your Item class would look like this:
public class Item
{
[JsonConverter(typeof(CrazyStringConverter))]
public string Name { get; set; }
public Guid? Id { get; set; }
[JsonExtensionData]
public Dictionary<string, object> CustomFields
{
get
{
if (_customFields == null)
_customFields = new Dictionary<string, object>();
return _customFields;
}
private set
{
_customFields = value;
}
}
private Dictionary<string, object> _customFields;
}
Then you can just deserialize it as normal. Demo:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""Item"":
{
""Name"":""Apple"",
""Id"":""4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac"",
""Size"":5,
""Quality"":2
}
}";
Item item = JsonConvert.DeserializeObject<Wrapper>(json).Item;
Console.WriteLine("Name: " + item.Name);
Console.WriteLine("Id: " + item.Id);
foreach (KeyValuePair<string, object> kvp in item.CustomFields)
{
Console.WriteLine(kvp.Key + ": " + kvp.Value);
}
}
}
public class Wrapper
{
public Item Item { get; set; }
}
class CrazyStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
// Reverse the string just for fun
return new string(token.ToString().Reverse().ToArray());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Output:
Name: elppA
Id: 4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac
Size: 5
Quality: 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With