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

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.

Related

Why does Asp WebApi HttpGet lose derived class properties? What's up with polymorphism? [duplicate]

The JSON response from my ASP.NET Core 3.1 API controller is missing properties. This happens when a property uses a derived type; any properties defined in the derived type but not in the base/interface will not be serialized to JSON. It seems there is some lack of support for polymorphism in the response, as if serialization is based on a property's defined type instead of its runtime type. How can I change this behavior to ensure that all public properties are included in the JSON response?
Example:
My .NET Core Web API Controller returns this object that has a property with an interface type.
// controller returns this object
public class Result
{
public IResultProperty ResultProperty { get; set; } // property uses an interface type
}
public interface IResultProperty
{ }
Here is a derived type that defines a new public property named Value.
public class StringResultProperty : IResultProperty
{
public string Value { get; set; }
}
If I return the derived type from my controller like this:
return new MainResult {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
then the actual response includes an empty object (the Value property is missing):
I want the response to be:
{
"ResultProperty": { "Value": "Hi there!" }
}
While the other answers are good and solves the problem, if all you want is the general behavior to be like pre netcore3, you can use the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package and in Startup.cs do:
services.AddControllers().AddNewtonsoftJson()
More info here. This way, you don't need to create any extra json-converters.
I ended up creating a custom JsonConverter (System.Text.Json.Serialization namespace) which forces JsonSerializer to serialize to the object's runtime type. See the Solution section below. It's lengthy but it works well and does not require me to sacrifice object oriented principles in my API's design. (If you need something quicker and can use Newtonsoft then check out the top voted answer instead.)
Some background: Microsoft has a System.Text.Json serialization guide with a section titled Serialize properties of derived classes with good information relevant to my question. In particular it explains why properties of derived types are not serialized:
This behavior is intended to help prevent accidental exposure of data
in a derived runtime-created type.
If that is not a concern for you then the behavior can be overridden in the call to JsonSerializer.Serialize by either explicitly specifying the derived type or by specifying object, for example:
// by specifying the derived type
jsonString = JsonSerializer.Serialize(objToSerialize, objToSerialize.GetType(), serializeOptions);
// or specifying 'object' works too
jsonString = JsonSerializer.Serialize<object>(objToSerialize, serializeOptions);
To accomplish this with ASP.NET Core you need to hook into the serialization process. I did this with a custom JsonConverter that calls JsonSerializer.Serialize one of the ways shown above. I also implemented support for deserialization which, while not explicitly asked for in the original question, is almost always needed anyway. (Oddly, supporting only serialization and not deserialization proved to be tricky anyway.)
Solution
I created a base class, DerivedTypeJsonConverter, which contains all of the serialization & deserialization logic. For each of your base types, you would create a corresponding converter class for it that derives from DerivedTypeJsonConverter. This is explained in the numbered directions below.
This solution follows the "type name handling" convention from Json.NET which introduces support for polymorphism to JSON. It works by including an additional $type property in the derived type's JSON (ex: "$type":"StringResultProperty") that tells the converter what the object's true type is. (One difference: in Json.NET, $type's value is a fully qualified type + assembly name, whereas my $type is a custom string which helps future-proof against namespace/assembly/class name changes.) API callers are expected to include $type properties in their JSON requests for derived types. The serialization logic solves my original problem by ensuring that all of the object's public properties are serialized, and for consistency the $type property is also serialized.
Directions:
1) Copy the DerivedTypeJsonConverter class below into your project.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
public abstract class DerivedTypeJsonConverter<TBase> : JsonConverter<TBase>
{
protected abstract string TypeToName(Type type);
protected abstract Type NameToType(string typeName);
private const string TypePropertyName = "$type";
public override bool CanConvert(Type objectType)
{
return typeof(TBase) == objectType;
}
public override TBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// get the $type value by parsing the JSON string into a JsonDocument
JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
jsonDocument.RootElement.TryGetProperty(TypePropertyName, out JsonElement typeNameElement);
string typeName = (typeNameElement.ValueKind == JsonValueKind.String) ? typeNameElement.GetString() : null;
if (string.IsNullOrWhiteSpace(typeName)) throw new InvalidOperationException($"Missing or invalid value for {TypePropertyName} (base type {typeof(TBase).FullName}).");
// get the JSON text that was read by the JsonDocument
string json;
using (var stream = new MemoryStream())
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Encoder = options.Encoder })) {
jsonDocument.WriteTo(writer);
writer.Flush();
json = Encoding.UTF8.GetString(stream.ToArray());
}
// deserialize the JSON to the type specified by $type
try {
return (TBase)JsonSerializer.Deserialize(json, NameToType(typeName), options);
}
catch (Exception ex) {
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
{
// create an ExpandoObject from the value to serialize so we can dynamically add a $type property to it
ExpandoObject expando = ToExpandoObject(value);
expando.TryAdd(TypePropertyName, TypeToName(value.GetType()));
// serialize the expando
JsonSerializer.Serialize(writer, expando, options);
}
private static ExpandoObject ToExpandoObject(object obj)
{
var expando = new ExpandoObject();
if (obj != null) {
// copy all public properties
foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)) {
expando.TryAdd(property.Name, property.GetValue(obj));
}
}
return expando;
}
}
2) For each of your base types, create a class that derives from DerivedTypeJsonConverter. Implement the 2 abstract methods which are for mapping $type strings to actual types. Here is an example for my IResultProperty interface that you can follow.
public class ResultPropertyJsonConverter : DerivedTypeJsonConverter<IResultProperty>
{
protected override Type NameToType(string typeName)
{
return typeName switch
{
// map string values to types
nameof(StringResultProperty) => typeof(StringResultProperty)
// TODO: Create a case for each derived type
};
}
protected override string TypeToName(Type type)
{
// map types to string values
if (type == typeof(StringResultProperty)) return nameof(StringResultProperty);
// TODO: Create a condition for each derived type
}
}
3) Register the converters in Startup.cs.
services.AddControllers()
.AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new ResultPropertyJsonConverter());
// TODO: Add each converter
});
4) In requests to the API, objects of derived types will need to include a $type property. Example JSON: { "Value":"Hi!", "$type":"StringResultProperty" }
Full gist here
The documentation shows how to serialize as the derived class when calling the serializer directly. The same technique can also be used in a custom converter that we then can tag our classes with.
First, create a custom converter
public class AsRuntimeTypeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options);
}
}
Then mark the relevant classes to be used with the new converter
[JsonConverter(typeof(AsRuntimeTypeConverter<MyBaseClass>))]
public class MyBaseClass
{
...
Alternately, the converter can be registered in startup.cs instead
services
.AddControllers(options =>
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new AsRuntimeTypeConverter<MyBaseClass>());
}));
I had a similar issue, where I was returning an enumerable of type TAnimal (but the object instances were of derived types such as Dog, Cat, etc.):
[HttpGet]
public IEnumerable<TAnimal> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list;
}
This only included properties defined in TAnimal.
However, in ASP .NET Core 3.1 at least, I found that I could just cast the object instances to object, and the JSON serializer then included all the properties from the derived classes:
[HttpGet]
public IEnumerable<object> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list.Select(a => (object)a);
}
(Note that the signature of the GetAnimals method must also changed, but that doesn't usually matter much in a web API context). If you need to provide type information for Swagger or whatever, you can annotate the method:
[HttpGet]
[Produces(MediaTypeNames.Application.Json, Type = typeof(TAnimal[]))]
public IEnumerable<object> GetAnimals()
{
...
}
Casting to object is a simple solution if you only have a 1-layer-deep object hierarchy to worry about.
This is the expected result. You're upcasting when you do that, so what will be serialized is the upcasted object, not the actual derived type. If you need stuff from the derived type, then that has to be the type of the property. You may want to use generics for this reason. In other words:
public class Result<TResultProperty>
where TResultProperty : IResultProperty
{
public TResultProperty ResultProperty { get; set; } // property uses an interface type
}
Then:
return new Result<StringResultProperty> {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
I solved it by writing this extension:
public static class JsonSerializationExtensions
{
public static string ToJson<T>(this IEnumerable<T> enumerable, bool includeDerivedTypesProperties = true)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
if (includeDerivedTypeProperties)
{
var collection = enumerable.Select(e => e as object).ToList();
return JsonSerializer.Serialize<object>(collection, jsonOptions);
}
else
{
return JsonSerializer.Serialize(enumerable, jsonOptions);
}
}
}
I was also struggling with this in a .NET Core 3.1 API, where I wanted the result to include $type attribute.
As suggested, install the correct package and then 'AddNewtonsoftJson'.
I wanted the $type field to be added to show the derived type handling, to get that
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
});
Not knocking Newtonsoft, but I found an easier way to resolve this with the built handlers.
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/emps", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
List<emp> GetEmps();
//[DataContract(Namespace = "foo")] <<< comment/removed this line
public class emp
{
public string userId { get; set; }
public string firstName { get; set; }
}
public class dept
{
public string deptId{ get; set; }
public string deptName{ get; set; }
}
In my case dept objects where working fine, but emp ones were not - they came across as empty.

Deserialization of JSON to List of Interface with generic type parameter

As the title mentions, I am trying to deserialize a JSON but am having some trouble. I think below includes the necessary information.
public class Variable<T> : IVariable where T : IConvertible
{
//...
}
public class ArrayVariable<T> : IVariable where T : IConvertible
{
//...
}
So I have a list of IVariable which I then serialize successfully (all of the information is in the json):
JsonConvert.SerializeObject(myIVariableList)
Now I am trying to deserialize it but I am having trouble determining the correct way to go about doing it as it involves finding the generic type T in addition to the type Variable or ArrayVariable. I have already tried
JsonConvert.DeserializeObject<List<IVariable>>(result.newValues)
but obviously, you can create instances of an interface. Any help would be much appreciated.
You can use TypeNameHandling.All but I would strongly recommend you avoid it due to it being very dangerous and allows attackers to compromise your code.
Another safer option is to use a custom converter. Here's a very trivial (and fragile) example that should get you started:
First lets make some basic classes that share an interface:
public interface IVariable { }
public class Foo : IVariable
{
public int A { get; set; }
}
public class Bar : IVariable
{
public int B { get; set; }
}
Now we can make our converter:
public class IVariableConverter : JsonConverter<IVariable>
{
public override IVariable ReadJson(JsonReader reader, Type objectType,
IVariable existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// First load the JSON into a JObject
var variable = JObject.Load(reader);
// If the JSON had a property called A, it must be a Foo:
if (variable.ContainsKey("A"))
{
return variable.ToObject<Foo>();
}
// If the JSON had a property called B, it must be a Bar:
if (variable.ContainsKey("B"))
{
return variable.ToObject<Bar>();
}
// And who knows what was passed in if it was missing both of those properties?!
throw new Exception("Er, no idea what that JSON was supposed to be!");
}
public override void WriteJson(JsonWriter writer, IVariable value,
JsonSerializer serializer)
{
// Feel free to write your own code here if you need it
throw new NotImplementedException();
}
}
And now we can do some actual deserialising:
// A basic JSON example:
var json = "[{\"A\":1},{\"B\":2}]";
// The settings to tell the serialiser how to process an IVariable object
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new IVariableConverter() }
};
// And deserialise with the defined settings
var result = JsonConvert.DeserializeObject<List<IVariable>>(json, settings);
You will need to be a bit more creative with how you identify each type, but this is a safe way to achieve what you need.
You can use TypeNameHandling.All to add type information to your serialiazed json and then utilize it during parsing:
var variables = new List<IVariable>()
{
new Variable<int>()
};
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var serializeObject = JsonConvert.SerializeObject(variables, settings);
var list = JsonConvert.DeserializeObject<List<IVariable>>(serializeObject, settings);

context.GetInput<model> returns base class instead of specified derived class in $Type

Using Azure Durable Functions, I am trying to use the context.GetInput<model>() function which returns the specified model. The model being used has a parameter that is another model which is a derived class. The model that is outputted from context.GetInput<model>() returns the model with the base class instead of the derived class.
I have checked the $type specified in the context, which shows the derived class, but when checking the outputted model, the result is the base class.
for example:
public class Student{
public Book book {get;set;}
}
public class Textbook:Book {
public string classfor {get;set;}
}
public class Book {
public string title {get;set;}
}
[ActivityTrigger] DurableActivityContextBase context is a parameter to the function.
Then I would be calling :
var model = context.GetInput<Student>()
where the context includes
{
"$type": "Student",
"book" : {
"$type": "Textbook",
"classfor" : "Math",
"title" : "PreAlgebra"
}
}
Yet the result is
Model of student which contains a Book instead of Textbook, where the title is assigned "PreAlgebra"
I expect the output of Student model to have a Textbook with properties:
title = "PreAlgebra"
classfor = "Math"
but the actual Student output contains a Book with the property
title = "PreAlgebra"
I've encountered the same problem you did last week. Unfortunately right now Azure Functions (even 2.x) don't support polymorphism for durable functions. The durable context serializes your object to JSON, but there's no way to pass JSON serialization settings as described here on GitHub. There's also another issue about this specific problem.
In my case I have an abstract base class, but you can use the same approach for your derived types. You can create a custom JSON converter that will deal with picking the correct type during deserialization. So for example if you have this sort of inheritance:
[JsonConverter(typeof(DerivedTypeConverter))]
public abstract class Base
{
[JsonProperty("$type")]
public abstract string Type { get; }
}
public class Child : Base
{
public override string Type => nameof(Child);
}
public class Child2 : Base
{
public override string Type => nameof(Child2);
}
Then you can have your а JSON Converter:
public class BaseDerivedTypeConverter : DefaultContractResolver
{
// You need this to protect yourself against circular dependencies
protected override JsonConverter ResolveContractConverter(Type objectType)
{
return typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract
? null
: base.ResolveContractConverter(objectType);
}
}
public class DerivedTypeConverter : JsonConverter
{
private static readonly JsonSerializerSettings Settings =
new JsonSerializerSettings()
{
ContractResolver = new BaseDerivedTypeConverter()
};
public override bool CanConvert(Type objectType) => (objectType == typeof(Base));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
// Make checks if jsonObject["$type"].Value<string>() has a supported type
// You can have a static dictionary or a const array of supported types
// You can leverage the array or dictionary to get the type you want again
var type = Type.GetType("Full namespace to the type you want", false); // the false flag means that the method call won't throw an exception on error
if (type != null)
{
return JsonConvert.DeserializeObject(jsonObject.ToString(), type, Settings);
}
else
{
throw new ValidationException("No valid $type has been specified!");
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
In my usage when I call context.GetInput<Base>() I can get either Child or Child1 because Base is abstract.
In your case it can be Book or Student depending on what's the actual value. This also applies for other durable function operations like like
var foobar = await context.CallActivityAsync<Base>("FuncName", context.GetInput<int>());
The converter will deal with that and you'll get the object you want inside foobar.
Per my understanding, the class Textbook extends Book, so "Book" is parent class and "Textbook" is subclass. In your context, you want to turn the child class(Textbook) to the parent class(Book). After that, "book" will just have the attribute "title" which is their common attribute but doesn't have the specific attribute "classfor". You can refer to the code below:
Tracked the updates to pass in Json serialization to Azure Functions here showing that it will be in v2.1!

Include Class name as part of the serialization in JSON C#

I have a specific situation where I need to include the class name as property in JSON when classes are serialized. The tricky parts is I need to do this dynamically. I can't just create an anonymous class before calling serialization.
I have decorated my class with a custom attribute as below:
[OntologyArea("con", " http://someurl/area/DomicileAddress")]
public class DomicileAddress : IContactPoint
{
[JsonProperty(PropertyName = "con:hasAddressPoint")]
public IAddressPoint AddressPoint
{
get; set;
}
}
In the above example the OntologyArea attribute should be read and included as a Property. The propery name should be the first argument of OntologyArea + the class name (i.e con:DomicileAddress) and the value should be the concrete class of IAddressPoint.
The tricky part is that the concrete class of IAddressPoint might need to do the same as shown here:
[OntologyArea("geo", "http://someurl.net/geolocation")]
public class StreetAddress : IAddressPoint
{
[JsonProperty("geo:hasStartingStreetNumber")]
public string StartingStreetNumber
{
get; set;
}
}
an example of JSON:
"con:DomicileAddress" : {
"con:hasAddressPoint" : {
"geo:StreetAddress" : {
"geo:hasEndingStreetNumber" : ""
}
}
}
So if any object does have a OntologyArea attribute I need to add a parent level. If it does not contain this attribute normal serilization should continue.
Please let me know If I need to explain more.
Is this solution for specifying the concrete type is a must or is it just your proposed solution?
Because Json.NET has a built-in support for encoding the actual types for properties of interface types or base classes:
var json = JsonConvert.SerializeObject(myContract, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
If you really need a completely custom logic you have to implement a converter, which also can be passed to the JsonSerializerSettings. It must be derived from JsonConverter and you have to implement the WriteJson method to emit your desired json sting using low level tokens just like in case of an XmlWriter:
private class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IAddressPoint).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var address = (IAddressPoint)value;
writer.WriteStartObject(); // {
writer.WritePropertyName($"geo:{address.GetType().Name}"); // "geo:StreetAddress"
// ... etc.
writer.WriteEndObject(); // }
// or you can just emit raw string:
writer.WriteRaw(myJsonBody);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
{
// todo: and the deserialization part goes here
}
}
This pointed me in the correct direction. I have an attribute that is read in this class and appended to the writer object.

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.

Categories