Cosmos DB Save Documents With Interface properties .NET SDK V3. How do I Save A Document? - json.net

I have run into problems saving documents with Properties that are Interfaces' I am receiving
this error.
Could not create an instance of type GTA__Tests.ItemTests.IMyCommand. Type is an interface or abstract class and cannot be instantiated.
My Interface:
public interface IMyCommand
{
[JsonProperty(PropertyName = "$type")]
string CommandType { get; }
}
My implementation:
public class MyCommand1 : IMyCommand
{
[JsonProperty("$type")]
public string CommandType => nameof(MyCommand1);
}
public class MyCommand2 : IMyCommand
{
[JsonProperty("$type")]
public string CommandType => nameof(MyCommand2);
}
//partition key is CompanyId
public class MyClass
{
[JsonProperty("id")]
public Guid Id { get; set; }
public string CompanyId { get; set; }
public List<IMyCommand> Commands { get; set; }
public MyClass(string partitionKey)
{
CompanyId = partitionKey;
}
}
My test code: Using NUnit
//Using cosmosdb .net sdk v3
public class Item_Tests
{
private string url;
private string key;
private string dbName;
private string containerName;
private string partitionKey = "Test";
CosmosClient client;
Container db;
[SetUp]
public void SetUp()
{
var config = Config.GetConfig();
url = config["CosmosLive:Url"];
key = config["CosmosLive:Key"];
dbName = config["CosmosLive:DbName"];
containerName = config["CosmosLive:ContainerName"];
client = new CosmosClient(url, key);
db = client.GetContainer(dbName, containerName);
}
[Test]
public async Task SaveAMyClassDocumentToCosmosDb_ReturnsOK()
{
var myclass = new MyClass(partitionKey)
{
Id = Guid.NewGuid(),
Commands = new List<IMyCommand>
{
new MyCommand1(),
new MyCommand2()
}
};
var result = await db.UpsertItemAsync<MyClass>(myclass, new
PartitionKey(partitionKey));
Assert.IsTrue(result.StatusCode is System.Net.HttpStatusCode.Created);
}
This produces a Newtonsoft.Json.JsonSerializationException
Where is my code wrong?
This is the full excetion:
Newtonsoft.Json.JsonSerializationException: Could not create an instance of type GTA__Tests.ItemTests.IMyCommand. Type is an interface or abstract class and cannot be instantiated. Path 'Commands[0]', line 1, position 103.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader)
at Microsoft.Azure.Cosmos.CosmosJsonDotNetSerializer.FromStream[T](Stream stream)
at Microsoft.Azure.Cosmos.CosmosJsonSerializerWrapper.FromStream[T](Stream stream)
at Microsoft.Azure.Cosmos.CosmosSerializerCore.FromStream[T](Stream stream)
at Microsoft.Azure.Cosmos.CosmosResponseFactoryCore.ToObjectpublic[T](ResponseMessage responseMessage)
at Microsoft.Azure.Cosmos.CosmosResponseFactoryCore.<CreateItemResponse>b__8_0[T](ResponseMessage cosmosResponseMessage)
at Microsoft.Azure.Cosmos.CosmosResponseFactoryCore.ProcessMessage[T](ResponseMessage responseMessage, Func`2 createResponse)
at Microsoft.Azure.Cosmos.CosmosResponseFactoryCore.CreateItemResponse[T](ResponseMessage responseMessage)
at Microsoft.Azure.Cosmos.ContainerCore.UpsertItemAsync[T](T item, ITrace trace, Nullable`1 partitionKey, ItemRequestOptions requestOptions, CancellationToken cancellationToken)
at Microsoft.Azure.Cosmos.ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](ITrace trace, Func`2 task, Func`2 openTelemetry, String operationName, RequestOptions requestOptions)
at Microsoft.Azure.Cosmos.ClientContextCore.OperationHelperWithRootTraceAsync[TResult](String operationName, RequestOptions requestOptions, Func`2 task, Func`2 openTelemetry, TraceComponent traceComponent, TraceLevel traceLevel)
at GTA__Tests.ItemTests.Item_Tests.SaveAMyClassDocumentToCosmosDb_ReturnsOK() in C:\Users\pjsta\source\repos\GosportTaxiApp\GTA__Tests\ItemTests\Item_Tests.cs:line 142

Your problem is that you are not implementing the "$type" property correctly. It isn't a property you add yourself, it's a synthetic property that Json.NET inserts when you set TypeNameHandling. This matters because UpsertItemAsync<T> is a round-trip operation: it serializes your MyClass, upserts it to azure, then deserializes the upserted result. Your implementation fails during deserialization.
To enable TypeNameHandling correctly for your MyClass.Commands list, remove all the CommandType properties and apply the attribute JsonPropertyAttribute.ItemTypeNameHandling like so:
public interface IMyCommand
{
}
public class MyCommand1 : IMyCommand
{
}
public class MyCommand2 : IMyCommand
{
}
//partition key is CompanyId
public class MyClass
{
[JsonProperty("id")]
public Guid Id { get; set; }
public string CompanyId { get; set; }
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)] // Add this
public List<IMyCommand> Commands { get; set; }
public MyClass(string partitionKey)
{
CompanyId = partitionKey;
}
}
Demo fiddle #1 here.
Now, it also seems as though you want to control the value of the "$type" properties, possibly to avoid the security risks related to TypeNameHandling. Under normal circumstances, one would create a custom serialization binder such as the following:
public class KnownTypesSerializationBinder : ISerializationBinder
{
Dictionary<Type, string> typesToNames;
Dictionary<string, Type> namesToTypes;
public KnownTypesSerializationBinder(IEnumerable<KeyValuePair<string, Type>> namesToTypes) =>
(this.namesToTypes, this.typesToNames) = (namesToTypes.ToDictionary(t => t.Key, t => t.Value), namesToTypes.ToDictionary(t => t.Value, t => t.Key));
public Type BindToType(string assemblyName, string typeName) => namesToTypes[typeName];
public void BindToName(Type serializedType, out string assemblyName, out string typeName) => (assemblyName, typeName) = (null, typesToNames[serializedType]);
}
Then one would create JsonSerializerSettings as follows:
var namesToTypes = new KeyValuePair<string, Type>[]
{
new(nameof(MyCommand1), typeof(MyCommand1)),
new(nameof(MyCommand2), typeof(MyCommand2)),
};
var settings = new JsonSerializerSettings
{
SerializationBinder = new KnownTypesSerializationBinder(namesToTypes),
};
However, azure-cosmos-dotnet-v3 seems to be designed to allow for Json.NET to be replaced with System.Text.Json (or any other serializer) in the future. As such, the type CosmosJsonDotNetSerializer has been made internal and CosmosSerializationOptions does not have any way to directly set JsonSerializerSettings. If you want to configure the serialization binder, I can imagine the following possibilities:
Implement your own version of CosmosJsonDotNetSerializer with the necessary settings and inject it via CosmosClientOptions.Serializer.
For an example of this approach, see e.g. this answer by Ankit Vijay to Serialize and Deserialize Cosmos DB document property as string.
Configure the binder indirectly by adding a converter to a parent type that configures JsonSerializer.SerializationBinder then continues with a default (de)serialization.
Abandon TypeNameHandling and create a custom converter for IMyCommand that implements its own custom type discriminator. For examples see Json.Net Serialization of Type with Polymorphic Child Object or Deserializing polymorphic json classes without type information using json.net.
Adopting the second approach, you will need to add the following converters, binders and associated extension methods:
internal class MyCommandSerializationBinderSettingConverter : KnownTypesJsonConverter<object>
{
public MyCommandSerializationBinderSettingConverter() : base(new KeyValuePair<string, Type>[]
{
new(nameof(MyCommand1), typeof(MyCommand1)),
new(nameof(MyCommand2), typeof(MyCommand2)),
}) { }
}
public class KnownTypesJsonConverter<TValue> : RecursiveConverterBase<TValue>
{
readonly ISerializationBinder binder;
public KnownTypesJsonConverter(IEnumerable<KeyValuePair<string, Type>> namesToTypes) => this.binder = new KnownTypesSerializationBinder(namesToTypes);
public KnownTypesJsonConverter(ISerializationBinder binder) => this.binder = binder;
protected override void WriteJsonWithDefault(JsonWriter writer, TValue value, JsonSerializer serializer)
{
var old = serializer.SerializationBinder;
try
{
serializer.SerializationBinder = binder;
base.WriteJsonWithDefault(writer, value, serializer);
}
finally
{
serializer.SerializationBinder = old;
}
}
protected override TValue ReadJsonWithDefault(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var old = serializer.SerializationBinder;
try
{
serializer.SerializationBinder = binder;
return base.ReadJsonWithDefault(reader, objectType, existingValue, serializer);
}
finally
{
serializer.SerializationBinder = old;
}
}
}
public abstract class RecursiveConverterBase<TValue> : JsonConverter
{
static readonly ThreadLocal<Stack<Type>> typeStack = new (() => new Stack<Type>());
bool IsSuspended => typeStack.IsValueCreated && typeStack.Value.TryPeek(out var type) && type == GetType();
public override bool CanRead => !IsSuspended;
public override bool CanWrite => !IsSuspended;
public override bool CanConvert(Type objectType) => !IsSuspended && typeof(TValue).IsAssignableFrom(objectType); // Or typeof(TValue) == objectType if you prefer.
protected virtual void WriteJsonWithDefault(JsonWriter writer, TValue value, JsonSerializer serializer)
{
Console.WriteLine(value);
serializer.Serialize(writer, value, typeof(TValue));
}
public sealed override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (var pushValue = typeStack.Value.PushUsing(GetType()))
{
WriteJsonWithDefault(writer, (TValue)value, serializer);
}
}
protected virtual TValue ReadJsonWithDefault(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => (TValue)serializer.Deserialize(reader, objectType);
public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (var pushValue = typeStack.Value.PushUsing(GetType()))
{
return ReadJsonWithDefault(reader, objectType, existingValue, serializer);
}
}
}
public static class StackExtensions
{
public class PushValue<T> : IDisposable
{
readonly Stack<T> stack;
readonly int count;
public PushValue(T value, Stack<T> stack)
{
this.stack = stack;
this.count = stack.Count;
stack.Push(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (stack != null)
{
while (stack.Count > count)
stack.Pop();
}
}
}
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
{
if (stack == null)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
}
}
public class KnownTypesSerializationBinder : ISerializationBinder
{
Dictionary<Type, string> typesToNames;
Dictionary<string, Type> namesToTypes;
public KnownTypesSerializationBinder(IEnumerable<KeyValuePair<string, Type>> namesToTypes) =>
(this.namesToTypes, this.typesToNames) = (namesToTypes.ToDictionary(t => t.Key, t => t.Value), namesToTypes.ToDictionary(t => t.Value, t => t.Key));
public Type BindToType(string assemblyName, string typeName) => namesToTypes[typeName];
public void BindToName(Type serializedType, out string assemblyName, out string typeName) => (assemblyName, typeName) = (null, typesToNames[serializedType]);
}
Then add MyCommandSerializationBinderSettingConverter to the container class MyClass like so:
[JsonConverter(typeof(MyCommandSerializationBinderSettingConverter))] // Add this
public class MyClass
{
[JsonProperty("id")]
public Guid Id { get; set; }
public string CompanyId { get; set; }
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)] // Add this
public List<IMyCommand> Commands { get; set; }
public MyClass(string partitionKey)
{
CompanyId = partitionKey;
}
}
And you will be able to serialize your IMyCommand polymorphic type hierarchy when contained inside a MyClass object using Json.NET with custom type names without needing to configure JsonSerializerSettings directly.
Second Json.NET demo fiddle here.
As an aside, if azure-cosmos-dotnet-v3 or its successor ever does transition to System.Text.Json, you will need to re-implement your classes as follows. First, add JsonDerivedTypeAttribute attributes to IMyCommand for each concrete implementation:
[System.Text.Json.Serialization.JsonDerivedType(typeof(MyCommand1), nameof(MyCommand1))]
[System.Text.Json.Serialization.JsonDerivedType(typeof(MyCommand2), nameof(MyCommand2))]
public interface IMyCommand
{
}
Second, fix the MyClass constructor so that the constructor arguments have the same case-invariant names as the corresponding properties:
public class MyClass
{
[System.Text.Json.Serialization.JsonPropertyName("id")]
public Guid Id { get; set; }
public string CompanyId { get; set; }
public List<IMyCommand> Commands { get; set; }
public MyClass(string companyId) // Change the argument name to match the property name
{
CompanyId = companyId;
}
}
This looks simpler than the Json.NET implementation because System.Text.Json's support for polymorphism only allows for whitelisted types with specified type discriminator values -- which is exactly the situation you are in.
Demo using System.Text.Json here.

Related

Creating a custom converter to write json as a Dictionary<string, IObject> from an object of String Key IObject Value

I am trying to create a custom JsonConverter to follow a third party API with a rather complicated object structure, and am a bit hamstrung on a few things. Note I am using .NET 4.7.2, not Core.
I have source objects that look like this, which I have created as objects rather than dictionaries in order to easily integrate them with FluentValidation. The objects will represent json objects in the foreign API. The "Value" is an interfaced item which is eventually converted to one of about 20 concrete types.
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
The source json for such an object is a dictionary, whereby the Key is the property, and the value is one of the 20 object types, as so:
{
"checked": [
{
"elementType": "input",
"inputType": "checkbox",
"name": "another_checkbox",
"label": "Another checkbox",
"checked": true
}
]
}
In this case, the object in C# would look something like this:
PDItem item = new PDItem() {
Key = "checked",
Value = new List<IItem>() {
new ConcreteTypeA () {
elementType = "input",
inputType = "checkbox",
name = "another_checkbox",
label = "Another checkbox",
#checked = true
}
}
};
For reference, my "PDItem"s implement the following interfaces:
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem: IElement
{
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
I was able to convert some concrete types (without having to do any tricky dictionary to object conversions), below is a working example of the ElementConverter attacked to my IElement interface (IItem uses the same pattern, and the same JsonCreationConverter class):
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
case ElementTypeDescriptions.ThirdType:
return new ThirdType();
case ElementTypeDescriptions.FourthType:
case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What makes my scenario so tricky is not that I am converting objects to dictionaries and back, but that my object values are other concrete objects (interfaced). I want to write a custom JsonConverter to serialize and deserialize these objects, but have no idea how to read and write the json in the methods below, let alone if what I am attempting to do is even possible. Any assistance would be greatly appreciated!
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// DO STUFF
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// DO STUFF
}
}
EDITED PER DBC's request:
Apologies for the complicated question DBC, and I greatly appreciate your time! Obviously, I'm a bit new to posting to stack overflow (long time lurker as they say).
Below is full working code that will run in .net fiddle (or in a console application if you simply add a namespace and json.net 12.x packages to a new .NET 4.7.2 console project). Second apologies that it is still a bit long and complicated, It is actually greatly simplified thanks to the omission of the Validation code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class PDItem : IDictionaryObject<List<IItem>>
{
[JsonProperty]
public string Key { get; set; }
[JsonProperty]
public List<IItem> Value { get; set; }
}
public interface IDictionaryObject<T>
{
string Key { get; set; }
T Value { get; set; }
}
[JsonConverter(typeof(IItemConverter))]
public interface IItem : IElement
{
string itemType { get; }
}
[JsonConverter(typeof(ElementConverter))]
public interface IElement
{
string elementType { get; }
}
public class ElementConverter : JsonCreationConverter<IElement>
{
protected override IElement Create(Type objectType, JObject jObject)
{
//TODO: Add objects to ElementConverter as they come online.
switch (jObject["elementType"].Value<string>())
{
case ElementTypeDescriptions.FirstType:
return new FirstType();
case ElementTypeDescriptions.SecondType:
return new SecondType();
//case ElementTypeDescriptions.ThirdType:
// return new ThirdType();
//case ElementTypeDescriptions.FourthType:
//case ElementTypeDescriptions.FifthType:
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
public class IItemConverter : JsonCreationConverter<IItem>
{
protected override IItem Create(Type objectType, JObject jObject)
{
switch (jObject["itemType"].Value<string>())
{
case ItemTypeDescriptions.FirstItemType:
return new FirstItemType();
case ItemTypeDescriptions.SecondItemType:
return new SecondItemType();
default:
throw new NotImplementedException("This object type is not yet implemented.");
}
}
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ElementTypeDescriptions
{
public const string FirstType = "firstTypeId";
public const string SecondType = "secondTypeId";
public const string ThirdType = "thirdTypeId";
public const string FourthType = "fourthTypeId";
public const string FifthType = "fifthTypeId";
}
/// <summary>
/// Used constants rather than an enum to allow for use in switch statements. Provided by third party to us to identify their classes across the API.
/// </summary>
public class ItemTypeDescriptions
{
public const string FirstItemType = "firstItemTypeId";
public const string SecondItemType = "secondItemTypeId";
}
/*** CONCRETE OBJECTS ***/
public class FirstType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string name { get; set; }
}
public class SecondType : IElement
{
public string elementType { get { return ElementTypeDescriptions.FirstType; } }
public string label { get; set; }
}
public class FirstItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string reference { get; set; }
}
public class SecondItemType : IItem
{
public string elementType { get { return ElementTypeDescriptions.FourthType; } }
public string itemType { get { return ItemTypeDescriptions.FirstItemType; } }
public string database { get; set; }
}
/*** END CONCRETE OBJECTS ***/
public class PDItemConverter : JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
/// THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION
/// DO STUFF
throw new NotImplementedException("THIS CODE TO BE WRITTEN TO ANSWER THE QUESTION");
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class TestClass
{
public static void Test()
{
var json = GetJson();
var item = JsonConvert.DeserializeObject<PDItem>(json);
var json2 = JsonConvert.SerializeObject(item, Formatting.Indented);
Console.WriteLine(json2);
}
static string GetJson()
{
var json = #"{
""checked"": [
{
""elementType"": ""input"",
""inputType"": ""checkbox"",
""name"": ""another_checkbox"",
""label"": ""Another checkbox"",
""checked"": true
}
]
}
";
return json;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}
One of the easiest ways to write a JsonConverter is to map the object to be serialized to some DTO, then (de)serialize the DTO. Since your PDItem looks like a single dictionary key/value pair and is serialized like a dictionary, the easiest DTO to use would be an actual dictionary, namely Dictionary<string, List<IItem>>.
Thus your PDItemConverter can be written as follows:
public class PDItemConverter: JsonConverter<PDItem>
{
public override void WriteJson(JsonWriter writer, PDItem value, JsonSerializer serializer)
{
// Convert to a dictionary DTO and serialize
serializer.Serialize(writer, new Dictionary<string, List<IItem>> { { value.Key, value.Value } });
}
public override PDItem ReadJson(JsonReader reader, Type objectType, PDItem existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
// Deserialize as a dictionary DTO and map to a PDItem
var dto = serializer.Deserialize<Dictionary<string, List<IItem>>>(reader);
if (dto == null)
return null;
if (dto.Count != 1)
throw new JsonSerializationException(string.Format("Incorrect number of dictionary keys: {0}", dto.Count));
var pair = dto.First();
existingValue = hasExistingValue ? existingValue : new PDItem();
existingValue.Key = pair.Key;
existingValue.Value = pair.Value;
return existingValue;
}
}
Since you are (de)serializing using the incoming serializer any converters associated to nested types such as IItem will get picked up and used automatically.
In addition, in JsonCreationConverter<T> you need to override CanWrite and return false. This causes the serializer to fall back to default serialization when writing JSON as explained in this answer to How to use default serialization in a custom JsonConverter. Also, I don't recommend catching and swallowing JsonReaderException. This exception is thrown when the JSON file itself is malformed, e.g. by being truncated. Ignoring this exception and continuing can occasionally force Newtonsoft to fall into an infinite loop. Instead, propagate the exception up to the application:
public abstract class JsonCreationConverter<T> : JsonConverter
{
// Override CanWrite and return false
public override bool CanWrite { get { return false; } }
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Demo fiddle here.

Disable DateParseHandling on document deserialization

We have documents on DocumentDB that store a date on ISO 8601 format. These dates are stored as strings:
{
"CreatedOn": "2016-04-15T14:54:40Z",
"Title": "Some title",
"id": "xxx-xxx-xxxx-xxx-xxx-xxx"
}
We are using the latest Azure DocumentDB SDK (1.7.0) on a WebAPI that uses ASP.NET Core.
The C# class that maps our documents have the "CreatedOn" property as a string.
public class Item
{
public string CreatedOn { get; set; }
public string id { get; set; }
public string Title { get; set; }
}
The problem is that when we read a document and the SDK deserializes it, it tries to convert it to a DateTime and then back to a string. Resulting in:
{
"CreatedOn": "15/04/2016 14:54:40",
"Title": "Some title",
"id": "xxx-xxx-xxxx-xxx-xxx-xxx"
}
What I need is to the SDK to leave the values untouched. I tried setting the default SerializerSettings to avoid the date parsing:
services.AddMvc().AddJsonOptions(opts =>
{
opts.SerializerSettings.DateParseHandling = Newtonsoft.Json.DateParseHandling.None;
});
But it didn't work.
I tried using a JsonConverter attribute but the problem is that on the ReadJson override method, the reader already parsed the string value to a DateTime.
class StringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override bool CanRead
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
reader.Value <-- already a parsed DateTime
}
}
Any ideas on how to overcome this auto parsing?
JSON serializer settings can now be passed straight to DocumentClient constuctors, which allows for the flexibility you required.
Eventually found a workaround, since the JsonSettings are not accesible at this time, I'm using a JsonConverter:
public class StringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override bool CanRead
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType.Equals(typeof(DateTime)))
{
return ((DateTime)reader.Value).ToIso8601Date();
}
if (reader.ValueType.Equals(typeof(DateTimeOffset)))
{
return ((DateTimeOffset)reader.Value).DateTime.ToIso8601Date();
}
return (string)reader.Value;
}
}
With this simple extension:
public static class DateTimeExtensions
{
private const string DateTimeFormat = "{0}-{1}-{2}T{3}:{4}:{5}Z";
public static string ToIso8601Date(this DateTime date)
{
if (date.Equals(DateTime.MinValue))
{
return null;
}
return string.Format(
DateTimeFormat,
date.Year,
PadLeft(date.Month),
PadLeft(date.Day),
PadLeft(date.Hour),
PadLeft(date.Minute),
PadLeft(date.Second));
}
private static string PadLeft(int number)
{
if (number < 10)
{
return string.Format("0{0}", number);
}
return number.ToString(CultureInfo.InvariantCulture);
}
}

JSON .NET intercept property names on deserialization

I'm using WebAPI, which relies on JSON .NET for the JSON format. On the C# side I have a DTO that looks like this:
public class DTO1 : IManifestContainer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public HashSet<string> Manifest { get; private set; }
}
public interface IManifestContainer
{
HashSet<string> Manifest { get; }
}
The idea of the IManifestContainer interface is to keep track of the properties that the client is actually sending to the server in the JSON object. For example, if the client sends this JSON:
{"FirstName":"Jojo"}
The Manifest hashset will contain the "FirstName" only.
If the client sends:
{"FirstName":"Jojo", "LastName":"Jones"}
The Manifest hashset will contain both "FirstName" and "LastName".
I tried implementing a JsonConverter like this:
public class ManifestJsonConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
JObject jObject = JObject.Load(reader);
// Convert the JObject to a C# object??
// Passing the serializer will call this method again
object retVal = jObject.ToObject(objectType, serializer);
IManifestContainer manifestContainer = (IManifestContainer) retVal;
foreach (var jProperty in jObject.Properties())
{
manifestContainer.Manifest.Add(jProperty.Name);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return typeof (IManifestContainer).IsAssignableFrom(objectType);
}
}
I need to load the JObject to obtain all the properties coming from the client, but then I don't know how to create the instance of "objectType" (the C# DTO) from the JObject.
After reading this other post I came up with this implementation. This covers all cases.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
JObject jObject = JObject.Load(reader);
JArray manifestArray = new JArray();
foreach (var jProperty in jObject.Properties())
{
manifestArray.Add(jProperty.Name);
}
jObject["Manifest"] = manifestArray;
var retVal = Activator.CreateInstance(objectType);
serializer.Populate(jObject.CreateReader(), retVal);
return retVal;
}
You can try using the overload of JObject.ToObject that does not take a JsonSerializer. In that case the ToObject method will use a new serializer instance that does not know about the converter, which should allow it to work, as long as you have not decorated your DTO classes with [JsonConverter] attributes.
object retVal = jObject.ToObject(objectType);
If that doesn't work, your other option is to create the DTO instances manually and populate their properties via reflection.

How to Serialize a Dictionary<string,string> and not surround the value with quotes?

Example model:
public class Thing
{
[JsonProperty("foo")]
public string Foo {get;set;}
[JsonProperty("bars")]
public Dictionary<string,string> Bars {get;set;}
}
i want the output to look like this:
{"foo":"Foo Value", "bars":{"key1":key1Value,"key2":key2Value}}
The reason I want the values of the Dictionary to be without quotes is so I can pull the value from the client via jquery:
{"foo":"Foo Value", "bars":{"key1":$('#key1').val(),"key2":$('#key2').val()}}
Is this possible using Json.Net?
This is my implementation I came up with:
public class DictionaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Dictionary<string, string>);
}
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)
{
var items = (Dictionary<string, string>)value;
writer.WriteStartObject();
foreach (var item in items)
{
writer.WritePropertyName(item.Key);
writer.WriteRawValue(item.Value);
}
writer.WriteEndObject();
writer.Flush();
}
}
This post helped too.

Why Json.net does not use customized IsoDateTimeConverter?

I use Json.net to serialize my objects and I want to customize DateTime output:
Here is a small example:
[DataContract]
class x
{
[DataMember]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime datum = new DateTime(1232, 3, 23);
}
var dtc = new IsoDateTimeConverter();
dtc.DateTimeFormat = "yy";
JsonConvert.SerializeObject(new x(), dtc);
The result is {"datum":"1232-03-23T00:00:00"} instead of {"datum":"1232"}.
This works correctly (returning "32"):
return JsonConvert.SerializeObject(new DateTime(1232, 3, 23), dtc);
Where is the catch?
The catch is that the converter applied via [JsonConverter(typeof(IsoDateTimeConverter))] supersedes the converter passed into the serializer. This is documented in Serialization Attributes: JsonConverterAttribute:
The
JsonConverterAttribute
specifies which
JsonConverter
is used to convert an object.
The attribute can be placed on a class or a member. When placed on a
class, the JsonConverter specified by the attribute will be the
default way of serializing that class. When the attribute is on a
field or property, then the specified JsonConverter will always be
used to serialize that value.
The priority of which JsonConverter is used is member attribute, then
class attribute, and finally any converters passed to the
JsonSerializer.
As a workaround, in the ReadJson() and WriteJson() methods of the applied converter, one could check for a relevant converter in the serializer's list of converters, and if one is found, use it. The decorator pattern can be used to separate this logic from the underlying conversion logic. First, introduce:
public class OverridableJsonConverterDecorator : JsonConverterDecorator
{
public OverridableJsonConverterDecorator(Type jsonConverterType) : base(jsonConverterType) { }
public OverridableJsonConverterDecorator(JsonConverter converter) : base(converter) { }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
foreach (var converter in serializer.Converters)
{
if (converter == this)
{
Debug.WriteLine("Skipping identical " + converter.ToString());
continue;
}
if (converter.CanConvert(value.GetType()) && converter.CanWrite)
{
converter.WriteJson(writer, value, serializer);
return;
}
}
base.WriteJson(writer, value, serializer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
foreach (var converter in serializer.Converters)
{
if (converter == this)
{
Debug.WriteLine("Skipping identical " + converter.ToString());
continue;
}
if (converter.CanConvert(objectType) && converter.CanRead)
{
return converter.ReadJson(reader, objectType, existingValue, serializer);
}
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
public abstract class JsonConverterDecorator : JsonConverter
{
readonly JsonConverter converter;
public JsonConverterDecorator(Type jsonConverterType) : this((JsonConverter)Activator.CreateInstance(jsonConverterType)) { }
public JsonConverterDecorator(JsonConverter converter)
{
if (converter == null)
throw new ArgumentNullException();
this.converter = converter;
}
public override bool CanConvert(Type objectType)
{
return converter.CanConvert(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return converter.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
converter.WriteJson(writer, value, serializer);
}
public override bool CanRead { get { return converter.CanRead; } }
public override bool CanWrite { get { return converter.CanWrite; } }
}
Then, apply the decorator on top of IsoDateTimeConverter as follows:
[DataContract]
class x
{
[DataMember]
[JsonConverter(typeof(OverridableJsonConverterDecorator), typeof(IsoDateTimeConverter))]
public DateTime datum = new DateTime(1232, 3, 23);
}
Now the statically applied converter will be superseded as required. Sample fiddle.
Note that, for this specific test case, as of Json.NET 4.5.1 dates are serialized in ISO by default and IsoDateTimeConverter is no longer required. Forcing dates to be serialized in a specific format can be accomplished by setting JsonSerializerSettings.DateFormatString:
[DataContract]
class x
{
[DataMember]
public DateTime datum = new DateTime(1232, 3, 23);
}
var settings = new JsonSerializerSettings { DateFormatString = "yy" };
var json1 = JsonConvert.SerializeObject(new x(), settings);
Console.WriteLine(json1); // Prints {"datum":"32"}
var json2 = JsonConvert.SerializeObject(new x());
Console.WriteLine(json2); // Prints {"datum":"1232-03-23T00:00:00"}
Sample fiddle. Nevertheless the general question deserves an answer.

Resources