Deserializing IList objects with Newtonsoft JSON - c#

I'm using a ConcreteCollectionTypeConverter to deserialize objects of type IList. I'm probably doing it very wrong because I keep getting the exception "Cannot convert List to IList"
My object inteface looks something like this:
[JsonObject(MemberSerialization.OptIn)]
public IMyInterface {
[JsonProperty("associatedContact")]
[JsonConverter(typeof(ConcreteCollectionTypeConverter<IList<ISomeOtherInterface>, List<SomeOtherInterface>>))]
IList<ISomeOtherInterface> MyObject { get; set; }
}
My object implementation looks like this:
public MyImplementation : IMyInterface {
public List<SomeOtherImplementation> MyObject { get; set; }
public MyImplementations () {
MyObject = new List<SomeOtherImplementation>();
}
}
My Json Converter looks like this:
class ConcreteCollectionTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<TImplementation>(reader);
}
}
I'm calling the deserialization method somewhere else in my project like this:
var myDeserializedObject = JsonConvert.DeserializeObject<MyImplementation>(jsonObject.ToString());
My jsonObject mentioned above is correctly parsed.
I know this is possible because I've seen a comment here on SO from a user caliming he's done it (Casting interfaces for deserialization in JSON.NET - first comment of the accepted answer).
This approach works fine for non-collection items (i.e. deserializing an object of an interface type IIterface MyObject with JsonConverer(typeof(ConcreteTypeCOnverter)) works).
If I use only Lists instead of ILists, I don't even need the Type Converter, but my requirements are to assure usage of ICollections because of possible non standard implementations.

I believe this will work for what you need:
public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var res = serializer.Deserialize<List<TImplementation>>(reader);
return res.ConvertAll(x => (TInterface) x);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Related

Json.Net specify custom converter of a property of a generic class

To specify which converter Json.Net serializer should use when dealing with a property, we usually use JsonConverterAttribute, which is a nice and neat syntax.
However, because C# doesn't support the use of generic type parameters in Attribute, trouble occurs when the declaring class of property contains generic type parameters. Here's a simple example:
public class FooConverter<T> : JsonConverter<T> {
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) {
//Implementation
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) {
//Implementation
}
}
public class Bar<T>{
[JsonConverter(typeof(FooConverter<T>))] //CS0416 'T': an attribute argument cannot use type parameters
public T Value { get; set; }
}
I know I can instantiate a generic converter and pass it with JsonSerializerSettings when calling JsonConvert.DeserializeObject, but that requires the caller to know such implementation detail of the type being deserialized. What I'm writing is a library, I don't want to bother my user to manually add a converter each time he needs to deserialize a object of such type.
Is there any workaround when my custom converter do rely on the type parameter?
You'll need to use a non-generic converter, e.g.
public class FooConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// ...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// ...
}
public override bool CanRead
{
get { return true; } // or false if you don't need custom read
}
public override bool CanConvert(Type objectType)
{
// ...
}
}
Then you can use [FooConverter] as an attribute. It won't be type-safe but there's no other way to do this because of the unbound nature of generics. However, as long as you return an object of type objectType in the ReadJson method, you won't have any problems.
More information is here.

Conditionally applying JsonConverter

I have a data structure we use to make api update calls to TFS and we set the Value property as a generic which works well. We'd like to use enums in our client code as representations of some types but they serialise as integers. So there is the [JsonConvert(typeof(StringEnumConverter))] option, on a generic this is an issue as it tries to serialise all values as string against the enum type of the property. It looks like this:
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
Is it possible to conditionally apply the converter when the property T is enum only?
By implementing a custom converter and specifying this as the converter:
public class OnlyEnumStringConverter : StringEnumConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null && value.GetType().IsEnum)
{
base.WriteJson(writer, value, serializer);
}
else
{
serializer.Serialize(writer, value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType.IsEnum)
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
else
{
return serializer.Deserialize(reader, objectType);
}
}
}
Specifying it:
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonConverter(typeof(OnlyEnumStringConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
Might it be easier to "sideclass" this, have a WorkItemUpdateData and a WorkItemUpdateEnumData ? Then your conditional logic to have the JsonConverter apply can be made when creating the object rather than when serializing it.
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonProperty("value")]
public T Value { get; set; }
}
public class WorkItemUpdateEnumData<T> : WorkItemUpdate
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
You could also include something in the setters to ensure they're used correctly, like
if (!typeof(T).IsEnum) throw ...

Serialize from and deserialize into different object property from same JSON property

I'd like to serialize a property from a class into a json property, and when deserializing back, i'd like to read it to a different property.
Cannot figure out how to achieve this in json.net
Minimal example:
Backend converts a specially formatted json property into a numeric timestamp (firebase systemtimestamp).
I want to use the same abstract base class for both serialization and deserialization.
When serializing, i need to generate a "timestamp" json property with special values.
When deserializing, incoming json contains a number in "timestamp" property. I obviously need this to be read into a different class property.
Class:
[JsonObject(MemberSerialization.OptIn)]
public abstract class ItemBase
{
[JsonProperty("timestamp")]
private TimestampModel WriteTimestamp => TimestampModelInstance;
[JsonProperty("timestamp")]
private long ReadTimestamp { get; set; }
public DateTime Timestamp => DateTimeOffset.FromUnixTimeMilliseconds(ReadTimestamp).LocalDateTime;
}
I would need to somehow mark WriteTimestamp as "serialize only", and ReadTimestampas "deserialize only"
If anyone finds this useful, I resolved the issue with a custom JsonConverter.
This way I have only one class property that has the type of the read value (long), that always serializes into TimestampModelInstance (the special firebase structure) regardless of the actual value
Converter:
public class TimestampConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimestampModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject.FromObject(TimestampModelInstance).WriteTo(writer);
}
}
In Class:
[JsonProperty("timestamp")]
[JsonConverter(typeof(TimestampConverter))]
private long NumericTimestamp { get; set; }
public DateTime Timestamp =>
DateTimeOffset.FromUnixTimeMilliseconds(NumericTimestamp).LocalDateTime;

JsonConverter for Custom Deserialization

I have implemented a custom JsonConverter to deserialize concrete classes when all we know about is the interface that is using them. given this, I have overridden the ReadJson method with the following:
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IMyInterface));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
if (jsonObject["Type"].Value<string>() == "MyType")
return jsonObject.ToObject<MyConcrete>(serializer);
}
}
On the interface I have added the following decoration:
[JsonConverter(typeof(MyConverter))]
public interface IMyInterface { ... }
The issue I seem to be having is that when deserialization is attempted, I keep getting StackOverflow exception, and I think that it is going around this custom converter for each element in the JSON structure.. which there are quite a few(!)
If I remove the decoration of the interface and instead add the custom converter to the call the converter manually... it works fine!
var jsonSerializerSettings = new JsonSerializerSettings
{
Converters = { new MyConverter() }
};
Is there a way I can resolve this without calling the customer converter specifically? I am concerned about unexpected behavior on deserialization of objects that don't need the custom JsonConverter. Thoughts?

Can I get the attributes on a property from the WriteJson method of a custom JsonConverter?

I want to decorate my classes with custom attributes, and read them when I convert to json using json.net inside a custom JsonConverter. I'll then vary the serialization depending on this custom attribute.
public class MyCustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I want to get any attributes set on the property here.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Int64);
}
}
Another approach would be to specify my custom JsonConverter on the property using an attribute, but I don't want to do this because I want to inject some behaviour into the constructor of my custom JsonConverter by instantiating the converters in the JsonSerializer settings as below.
String json = JsonConvert.SerializeObject(new MyCLass(), new JsonSerializerSettings
{
Converters = new List
{
new MyCustomJsonConverter()
}
});
I can get to the name of the property in the textWriter path. And I can see some interesting hints in the documentation about Metadata, but I can't find a way to do this.
Here's an example decorated class:
public class MyCustomAttribute : Attribute { }
public class MyCLass
{
[MyCustom]
public Int64 MyInt { get; set; }
}
JsonConverters apply to types, not to fields or properties.
Instead of adding an attribute to a property that uses an existing type, consider creating a new type and writing a convert for that instead.
public struct MyCustomType
{
...
}
public class MyClass
{
public MyCustomType {get; set;}
}
Besides - in what other way would you ever want to serialize a raw integer? If the integer represents something, then create a struct or class for that something.
See also: "ValueObject" (Domain Driven Design fundamental concept)
Based on your comment below, an alternate approach would be to forget about JsonConverters and simply expose a secondary property:
public class MyClass
{
[JsonIgnore]
public Int64 MyInt {get; set;}
[JsonProperty("MyInt")]
public Int64 MyEncryptedInt
{
get { return Encrypt(MyInt); }
set { MyInt = Decrypt(value); }
}
}

Categories

Resources