How to get parent property name in System.Text.Json.Serialization.JsonConverter? - json

I'm dealing with a 3rd Party API that returns data like this:
{
"dynamicFields": {
"prop1": "val1",
"prop2": "val2",
"prop3": "val3"
},
// otherFields ...
}
Since these "dynamic fields" are not strongly typed. I want to represent my POCO as:
class ApiResponse {
public DynamicFields DynamicFields { get; get; }
}
class DynamicFields {
public Dictionary<string, string> Values { get; set; }
}
The rub is, I want to be able to prefix the keys in the dictionary, such that they have the parent property name like so:
{
"DynamicFields": {
"dynamicFields.prop1": "val1",
"dynamicFields.prop2": "val2",
"dynamicFields.prop3": "val2",
}
}
However, it doesn't look like System.Text.Json.Serialization.JsonConverter<T> gives me access to the parent property name. Is there another way to accomplish this such that I can get the desired output? I'm really trying to not use Newtonsoft/Json.NET as I believe System.Text.Json is "the future".
class DynamicFieldConverter : JsonConverter<DynamicField>
{
public override DynamicField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// How do I get the name of the parent property here?
...
}
public override void Write(Utf8JsonWriter writer, DynamicField value, JsonSerializerOptions options)
{
...
}
}

Here my solution for your case:
DynamicFieldConverter class
public class DynamicFieldConverter : JsonConverter<DynamicFields>
{
private const string MainPropName = "dynamicFields";
public override DynamicFields Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dictionary = new Dictionary<string, string>();
var startDepth = reader.CurrentDepth;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject && reader.CurrentDepth == startDepth)
{
return new DynamicFields
{
Values = dictionary
};
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
if(propertyName == MainPropName)
continue;
var value = JsonSerializer.Deserialize<string>(ref reader, options);
dictionary.Add($"{MainPropName}.{propertyName}", value);
}
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, DynamicFields value, JsonSerializerOptions options)
{
}
}
JsonConverterAttribute for class
[JsonConverter(typeof(DynamicFieldConverter))]
public class DynamicFields
{
public Dictionary<string, string> Values { get; set; }
}
Finally, console to test it
static void Main(string[] args)
{
var json = "{\r\n \"dynamicFields\": {\r\n \"prop1\": \"val1\",\r\n \"prop2\": \"val2\",\r\n \"prop3\": \"val3\"\r\n }\r\n}";
var obj = JsonSerializer.Deserialize<DynamicFields>(json);
}

Related

System.Text.Json serialize array of type to a json object with properties [duplicate]

This question already has an answer here:
json deserialization to C# with dynamic keys [duplicate]
(1 answer)
Closed 7 months ago.
Using System.Text.Json, how can I serialize (and deserialize) an array of n elements (classes), to an object of n children, where the element name is one of the properties of the object?
For example, by default the following classes
public class Project
{
public string Name {get;set;}
public Environment[] Environments {get;set;}
}
public class Environment
{
public string Name {get;set;}
public string Region {get;set;}
public Settings Settings {get;set;}
}
public class Settings
{
public bool OverrideOnFetch {get;set;}
}
will be serialized as
{
"name": "MyProject",
"environments": [
{
"name": "dev",
"region": "us1",
"settings": {
"overrideOnFetch": false
}
},
{
"name": "prod",
"region": "us1",
"settings": {
"overrideOnFetch": false
}
}
]
}
But I want to change this behavior and serialize it as
{
"name": "MyProject",
"environments": {
"dev": {
"region": "us1",
"settings": {
"overrideOnFetch": false
}
},
"prod": {
"region": "us1",
"settings": {
"overrideOnFetch": false
}
}
}
}
without changing the classes (or creating another). As you can see, the Environment.Name acts like the property of environments in JSON.
I have no idea where to continue from here
class ProjectJsonConverter : JsonConverter<Project>
{
public override Project? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, Project value, JsonSerializerOptions options)
{
var namePolicy = options.PropertyNamingPolicy ?? new DefaultJsonNamingPolicy();
writer.WriteStartObject(); //starts the "project" object
writer.WriteString(JsonEncodedText.Encode(namePolicy.ConvertName(nameof(value.Name))), value.Name);
//How to write the "value.Settings" without rewriting everything?
writer.WriteStartObject(); //starts the "environment" object
foreach(var env in value.Environments)
{
}
writer.WriteEndObject(); //ends the "environment" object
writer.WriteEndObject(); //ends the "project" object
}
class DefaultJsonNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
return name;
}
}
}
A much simpler solution than manually writing the JSON array, is to serialize and deserialize to/from a Dictionary within your converter. Then simply convert to/from a list or array type.
A generic version of this would be:
public class PropertyToObjectListConverter<T> : JsonConverter<ICollection<T>>
{
static PropertyInfo _nameProp =
typeof(T).GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(JsonPropertyToObjectAttribute)).Any())
?? typeof(T).GetProperty("Name");
static Func<T, string> _getter = _nameProp?.GetMethod?.CreateDelegate<Func<T, string>>() ?? throw new Exception("No getter available");
static Action<T, string> _setter = _nameProp?.SetMethod?.CreateDelegate<Action<T, string>>() ?? throw new Exception("No setter available");
public override bool CanConvert(Type typeToConvert) =>
typeof(ICollection<T>).IsAssignableFrom(typeToConvert);
public override ICollection<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dict = JsonSerializer.Deserialize<Dictionary<string, T>>(ref reader, options);
var coll = (ICollection<T>) Activator.CreateInstance(typeToConvert);
foreach (var kvp in dict)
{
var value = kvp.Value;
_setter(value, kvp.Key);
coll.Add(value);
}
return coll;
}
public override void Write(Utf8JsonWriter writer, ICollection<T> value, JsonSerializerOptions options)
{
var dict = value.ToDictionary(_getter);
JsonSerializer.Serialize(writer, dict, options);
}
}
[AttributeUsage(AttributeTargets.Property)]
public class JsonPropertyToObjectAttribute : Attribute
{
}
It looks first for a property with the JsonPropertyToObject attribute, otherwise it tries to find a property called Name. It must be a string type.
Note the use of static getter and setter functions to make the reflection fast, this only works on properties, not fields.
You would use it like this:
public class Project
{
public string Name {get;set;}
[JsonConverter(typeof(PropertyToObjectListConverter<Environment>))]
public Environment[] Environments {get;set;}
}
public class Environment
{
[JsonIgnore]
public string Name {get;set;}
public string Region {get;set;}
public Settings Settings {get;set;}
}
dotnetfiddle

Is it possible to tell Newtonsoft to map a declared $type to a different type when deserialising? [duplicate]

Suppose I have the following class hierarchy:
public abstract class Organization
{
/* properties related to all organizations */
}
public sealed class Company : Organization
{
/* properties related to companies */
}
public sealed class NonProfitOrganization : Organization
{
/* properties related to non profit organizations */
}
Is it possible to have json.net use property (say "type" or "discriminator") to determine which type the object when it deserializes the organization? For example, the following should deserialize an instance of Company.
{
"type": "company"
/* other properties related to companies */
}
And the following should deserialize an instance of NonProfitOrganization.
{
"type": "non-profit"
/* other properties related to non profit */
}
When I call the following:
Organization organization = JsonConvert.DeserializeObject<Organization>(payload);
where payload is the above JSON snippets. I had a look at setting the "TypeNameHandling" on properties or classes but it serializes the whole .NET type, which isn't "portable" between the client and server when the classes are defined in different namespaces and assemblies.
I'd rather define the type is a neutral manner which clients written in any language can use to determine the actual type of the object type being serialized.
In case you are still looking, here is an example: http://james.newtonking.com/archive/2011/11/19/json-net-4-0-release-4-bug-fixes.aspx
This will allow you to create a table based mapping:
public class TypeNameSerializationBinder : SerializationBinder
{
public TypeNameSerializationBinder(Dictionary<Type, string> typeNames = null)
{
if (typeNames != null)
{
foreach (var typeName in typeNames)
{
Map(typeName.Key, typeName.Value);
}
}
}
readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public void Map(Type type, string name)
{
this.typeToName.Add(type, name);
this.nameToType.Add(name, type);
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
var name = typeToName.Get(serializedType);
if (name != null)
{
assemblyName = null;
typeName = name;
}
else
{
assemblyName = serializedType.Assembly.FullName;
typeName = serializedType.FullName;
}
}
public override Type BindToType(string assemblyName, string typeName)
{
if (assemblyName == null)
{
var type = this.nameToType.Get(typeName);
if (type != null)
{
return type;
}
}
return Type.GetType(string.Format("{0}, {1}", typeName, assemblyName), true);
}
}
The code has a slight defect in that if a type name mapping is attempted where the type is unique but the name is already used, the Map method will throw an exception after the type-to-name mapping is already added leaving the table in an inconsistent state.
To take eulerfx's answer further; I wanted to apply DisplayName attribute to a class and have that automatically become the type name used; to that end:
public class DisplayNameSerializationBinder : DefaultSerializationBinder
{
private Dictionary<string, Type> _nameToType;
private Dictionary<Type, string> _typeToName;
public DisplayNameSerializationBinder()
{
var customDisplayNameTypes =
this.GetType()
.Assembly
//concat with references if desired
.GetTypes()
.Where(x => x
.GetCustomAttributes(false)
.Any(y => y is DisplayNameAttribute));
_nameToType = customDisplayNameTypes.ToDictionary(
t => t.GetCustomAttributes(false).OfType<DisplayNameAttribute>().First().DisplayName,
t => t);
_typeToName = _nameToType.ToDictionary(
t => t.Value,
t => t.Key);
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
if (false == _typeToName.ContainsKey(serializedType))
{
base.BindToName(serializedType, out assemblyName, out typeName);
return;
}
var name = _typeToName[serializedType];
assemblyName = null;
typeName = name;
}
public override Type BindToType(string assemblyName, string typeName)
{
if (_nameToType.ContainsKey(typeName))
return _nameToType[typeName];
return base.BindToType(assemblyName, typeName);
}
}
and usage example:
public class Parameter
{
public string Name { get; set; }
};
[DisplayName("bool")]
public class BooleanParameter : Parameter
{
}
[DisplayName("string")]
public class StringParameter : Parameter
{
public int MinLength { get; set; }
public int MaxLength { get; set; }
}
[DisplayName("number")]
public class NumberParameter : Parameter
{
public double Min { get; set; }
public double Max { get; set; }
public string Unit { get; set; }
}
[DisplayName("enum")]
public class EnumParameter : Parameter
{
public string[] Values { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
var parameters = new Parameter[]
{
new BooleanParameter() {Name = "alive"},
new StringParameter() {Name = "name", MinLength = 0, MaxLength = 10},
new NumberParameter() {Name = "age", Min = 0, Max = 120},
new EnumParameter() {Name = "status", Values = new[] {"Single", "Married"}}
};
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Binder = new DisplayNameSerializationBinder(),
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var json = JsonConvert.SerializeObject(parameters);
var loadedParams = JsonConvert.DeserializeObject<Parameter[]>(json);
Console.WriteLine(JsonConvert.SerializeObject(loadedParams));
}
}
output:
[
{
"$type": "bool",
"name": "alive"
},
{
"$type": "string",
"maxLength": 10,
"name": "name"
},
{
"$type": "number",
"max": 120.0,
"name": "age"
},
{
"$type": "enum",
"values": [
"Single",
"Married"
],
"name": "status"
}
]
I've written purely declarative solution with ability to specify custom discriminator field, and provide scoped name handling per base class (as opposed to usecure global JsonSerializationSettings, especially on different Web-Api when we do not have ability to specify custom JsonSerializationSettings).
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
// Discriminated Json Converter (JsonSubtypes) implementation for .NET
//
// MIT License
//
// Copyright (c) 2016 Anatoly Ressin
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
////////////////////// USAGE ////////////////////////////////////////////////////////////////////////////////
[JsonConverter(typeof(JsonSubtypes))] // Discriminated base class SHOULD NOT be abstract
public class ShapeBase {
[JsonTag, JsonProperty("#type")] // it SHOULD contain a property marked with [JsonTag]
public string Type {get;set;} // only one [JsonTag] annotation allowed per discriminated class
// it COULD contain other properties, however this is NOT RECOMMENDED
// Rationale: instances of this class will be created at deserialization
// only for tag sniffing, and then thrown away.
}
public abstract class Shape: ShapeBase { // If you want abstract parent - extend the root
public abstract double GetArea(); // with needed abstract stuff, then use this class everywhere (see DEMO below)
}
[JsonSubtype("circle")] // Every final class-case SHOULD be marked with [JsonSubtype(tagValue)]
public class Circle: Shape { // Two disctinct variant classes MUST have distinct tagValues
[JsonProperty("super-radius")] // You CAN use any Json-related annotation as well
public double Radius { get; set; }
public override double GetArea() {
return Radius * Radius * Math.PI;
}
}
[JsonSubtype("rectangle")]
public class Rectangle: Shape {
public double Height { get; set; }
public double Width { get; set; }
public override double GetArea() {
return Width * Height;
}
}
[JsonSubtype("group")]
public class Group: Shape {
[JsonProperty("shapes")]
public List<Shape> Items { get; set; }
public override double GetArea() {
return Items.Select(item => item.GetArea()).Sum();
}
}
// Every final class-case SHOULD be registered with JsonSubtypes.register(typeof(YourConcreteClass))
// either manually or with auto-register capability:
// You can auto-register all classes marked with [JsonSubtype(tag)] in given Assembly
// using JsonSubtypes.autoRegister(yourAssembly)
////////////////// DEMO /////////////////////////////////////////////////////////////////////////////////
public class Program
{
public static void Main()
{
JsonSubtypes.autoRegister(Assembly.GetExecutingAssembly());
Shape original = new Group() {
Items = new List<Shape> {
new Circle() { Radius = 5 },
new Rectangle() { Height = 10, Width = 20 }
}
};
string str = JsonConvert.SerializeObject(original);
Console.WriteLine(str);
var copy = JsonConvert.DeserializeObject(str,typeof(Shape)) as Shape;
// Note: we can deserialize object using any class from the hierarchy.
// Under the hood, anyway, it will be deserialized using the top-most
// base class annotated with [JsonConverter(typeof(JsonSubtypes))].
// Thus, only soft-casts ("as"-style) are safe here.
Console.WriteLine("original.area = {0}, copy.area = {1}", original.GetArea(), copy.GetArea());
}
}
//////////////////////// IMPLEMENTATION //////////////////////////////////////////////////////////////////
public class JsonSubtypeClashException: Exception {
public string TagValue { get; private set;}
public Type RootType { get; private set; }
public Type OldType { get; private set; }
public Type NewType { get; private set; }
public JsonSubtypeClashException(Type rootType, string tagValue, Type oldType, Type newType): base(
String.Format(
"JsonSubtype Clash for {0}[tag={1}]: oldType = {2}, newType = {3}",
rootType.FullName,
tagValue,
oldType.FullName,
newType.FullName
)
) {
TagValue = tagValue;
RootType = rootType;
OldType = oldType;
NewType = newType;
}
}
public class JsonSubtypeNoRootException: Exception {
public Type SubType { get; private set; }
public JsonSubtypeNoRootException(Type subType): base(
String.Format(
"{0} should be inherited from the class with the [JsonConverter(typeof(JsonSubtypes))] attribute",
subType.FullName
)
) {
SubType = subType;
}
}
public class JsonSubtypeNoTagException: Exception {
public Type SubType { get; private set; }
public JsonSubtypeNoTagException(Type subType): base(
String.Format(
#"{0} should have [JsonSubtype(""..."")] attribute",
subType.FullName
)
) {
SubType = subType;
}
}
public class JsonSubtypeNotRegisteredException: Exception {
public Type Root { get; private set; }
public string TagValue { get; private set; }
public JsonSubtypeNotRegisteredException(Type root, string tagValue): base(
String.Format(
#"Unknown tag={1} for class {0}",
root.FullName,
tagValue
)
) {
Root = root;
TagValue = tagValue;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class JsonSubtypeAttribute: Attribute {
private string tagValue;
public JsonSubtypeAttribute(string tagValue) {
this.tagValue = tagValue;
}
public string TagValue {
get {
return tagValue;
}
}
}
public static class JsonSubtypesExtension {
public static bool TryGetAttribute<T>(this Type t, out T attribute) where T: Attribute {
attribute = t.GetCustomAttributes(typeof(T), false).Cast<T>().FirstOrDefault();
return attribute != null;
}
private static Dictionary<Type, PropertyInfo> tagProperties = new Dictionary<Type, PropertyInfo>();
public static bool TryGetTagProperty(this Type t, out PropertyInfo tagProperty) {
if (!tagProperties.TryGetValue(t, out tagProperty)) {
JsonConverterAttribute conv;
if (t.TryGetAttribute(out conv) && conv.ConverterType == typeof(JsonSubtypes)) {
var props = (from prop in t.GetProperties() where prop.GetCustomAttribute(typeof(JsonTagAttribute)) != null select prop).ToArray();
if (props.Length == 0) throw new Exception("No tag");
if (props.Length > 1) throw new Exception("Multiple tags");
tagProperty = props[0];
} else {
tagProperty = null;
}
tagProperties[t] = tagProperty;
}
return tagProperty != null;
}
public static bool TryGetTagValue(this Type t, out string tagValue) {
JsonSubtypeAttribute subtype;
if (t.TryGetAttribute(out subtype)) {
tagValue = subtype.TagValue;
return true;
} else {
tagValue = null;
return false;
}
}
public static bool TryGetJsonRoot(this Type t, out Type root, out PropertyInfo tagProperty) {
root = t;
do {
if (root.TryGetTagProperty(out tagProperty)) {
return true;
}
root = root.BaseType;
} while (t != null);
return false;
}
}
public class JsonTagAttribute: Attribute {
}
public class JsonTagInfo {
public PropertyInfo Property { get; set; }
public string Value { get; set; }
}
public class JsonRootInfo {
public PropertyInfo Property { get; set; }
public Type Root { get; set; }
}
public abstract class DefaultJsonConverter: JsonConverter {
[ThreadStatic]
private static bool silentWrite;
[ThreadStatic]
private static bool silentRead;
public sealed override bool CanWrite {
get {
var canWrite = !silentWrite;
silentWrite = false;
return canWrite;
}
}
public sealed override bool CanRead {
get {
var canRead = !silentRead;
silentRead = false;
return canRead;
}
}
protected void _WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) {
silentWrite = true;
serializer.Serialize(writer, value);
}
protected Object _ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) {
silentRead = true;
return serializer.Deserialize(reader, objectType);
}
}
public class JsonSubtypes: DefaultJsonConverter {
private static Dictionary<Type, Dictionary<string, Type>> implementations = new Dictionary<Type, Dictionary<string, Type>>();
private static Dictionary<Type, JsonTagInfo> tags = new Dictionary<Type, JsonTagInfo>();
private static Dictionary<Type, JsonRootInfo> roots = new Dictionary<Type, JsonRootInfo>();
public static void register(Type newType) {
PropertyInfo tagProperty;
Type root;
if (newType.TryGetJsonRoot(out root, out tagProperty)) {
for(var t = newType; t != root; t = t.BaseType) {
roots[t] = new JsonRootInfo() {
Property = tagProperty,
Root = root
};
}
roots[root] = new JsonRootInfo() {
Property = tagProperty,
Root = root
};
Dictionary<string, Type> implementationMap;
if (!implementations.TryGetValue(root, out implementationMap)) {
implementationMap = new Dictionary<string, Type>();
implementations[root] = implementationMap;
}
JsonSubtypeAttribute attr;
if (!newType.TryGetAttribute(out attr)) {
throw new JsonSubtypeNoTagException(newType);
}
var tagValue = attr.TagValue;
Type oldType;
if (implementationMap.TryGetValue(tagValue, out oldType)) {
throw new JsonSubtypeClashException(root, tagValue, oldType, newType);
}
implementationMap[tagValue] = newType;
tags[newType] = new JsonTagInfo() {
Property = tagProperty,
Value = tagValue
};
} else {
throw new JsonSubtypeNoRootException(newType);
}
}
public static void autoRegister(Assembly assembly) {
foreach(var type in assembly.GetTypes().Where(type => type.GetCustomAttribute<JsonSubtypeAttribute>() != null)) {
register(type);
}
}
public override bool CanConvert(Type t) {
return true;
}
public static T EnsureTag<T>(T value) {
JsonTagInfo tagInfo;
if (tags.TryGetValue(value.GetType(), out tagInfo)) {
tagInfo.Property.SetValue(value, tagInfo.Value);
}
return value;
}
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer) {
_WriteJson(writer, EnsureTag(value), serializer);
}
public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) {
JsonTagInfo tagInfo;
if (tags.TryGetValue(objectType, out tagInfo)) {
return _ReadJson(reader, objectType, existingValue, serializer);
} else {
JsonRootInfo rootInfo;
if (roots.TryGetValue(objectType, out rootInfo)) {
JToken t = JToken.ReadFrom(reader);
var stub = _ReadJson(t.CreateReader(), rootInfo.Root, existingValue, serializer);
var tagValue = rootInfo.Property.GetValue(stub) as string;
var implementationMap = implementations[rootInfo.Root];
Type implementation;
if (implementationMap.TryGetValue(tagValue, out implementation)) {
return ReadJson(t.CreateReader(), implementation, null, serializer);
} else {
throw new JsonSubtypeNotRegisteredException(rootInfo.Root, tagValue);
}
} else {
return _ReadJson(reader, objectType, existingValue, serializer);
}
}
}
public static T Deserialize<T>(string s) where T: class {
return JsonConvert.DeserializeObject(s, typeof(T)) as T;
}
public static string Serialize<T>(T value) where T: class {
return JsonConvert.SerializeObject(value);
}
}
output:
{"shapes":[{"super-radius":5.0,"#type":"circle"},{"Height":10.0,"Width":20.0,"#type":"rectangle"}],"#type":"group"}
original.area = 278.539816339745, copy.area = 278.539816339745
You can grab it here:
https://dotnetfiddle.net/ELcvnk
With another JsonSubtypes converter implementation.
Usage:
[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
public virtual string Sound { get; }
public string Color { get; set; }
}
public class Dog : Animal
{
public override string Sound { get; } = "Bark";
public string Breed { get; set; }
}
public class Cat : Animal
{
public override string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}
[TestMethod]
public void Demo()
{
var input = #"{""Sound"":""Bark"",""Breed"":""Jack Russell Terrier""}"
var animal = JsonConvert.DeserializeObject<Animal>(input);
Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
}
the converter implementation can be directly downloaded from the repository: JsonSubtypes.cs and is also availble as a nuget package
Use this JsonKnownTypes, it's very similar way to use, add couple of attribute:
[JsonConverter(typeof(JsonKnownTypeConverter<Organization>))]
[JsonDiscriminator(Name = "discriminator")]
[JsonKnownType(typeof(Company), "company")]
[JsonKnownType(typeof(NonProfitOrganization), "non-profit")]
public abstract class Organization
{
/* properties related to all organizations */
}
public sealed class Company : Organization
{
/* properties related to companies */
}
public sealed class NonProfitOrganization : Organization
{
/* properties related to non profit organizations */
}
And serialize:
var json = JsonConvert.SerializeObject(youObject)
Output json:
{..., "discriminator":"non-profit"} //if object was NonProfitOrganization
Deserialization:
var organization = JsonConvert.DeserializeObject<Organization>(payload);

.NET NewtonSoft JSON deserialize with different property name [duplicate]

how can I deserialize below json structure using newtonsoft json.net in .net.
{
"users" : {
"parentname":"test",
"100034" : {
"name" : "tom",
"state" : "WA",
"id" : "cedf-c56f-18a4-4b1"
},
"10045" : {
"name" : "steve",
"state" : "NY",
"id" : "ebb2-92bf-3062-7774"
},
"12345" : {
"name" : "mike",
"state" : "MA",
"id" : "fb60-b34f-6dc8-aaf7"
}
}
}
I tried below code but its not working. I got error 'Error converting value "test" to type 'ConsoleApplication2.User'. Path 'users.parentname', line 5, position 35.'
class Program
{
static void Main(string[] args)
{
string json = #"
{
""users"": {
""parentname"":""test"",
""10045"": {
""name"": ""steve"",
""state"": ""NY"",
""id"": ""ebb2-92bf-3062-7774""
}
}
}";
RootObject root = JsonConvert.DeserializeObject<RootObject>(json);
}
}
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Please suggest.
You have a couple problems:
Your JSON has an extra level of nesting, with the root object containing a single property "users":
{
"users" : { ... }
}
Your data model needs to reflect this.
Your "users" object has a mixture of known and unknown property names. The question Deserialize json with known and unknown fields addresses a similar situation, however in your case your unknown properties always have a fixed schema and their values should be deserialized into a dictionary of POCOs -- specifically the User class. Therefore the answers there don't quite meet your needs, nor does the build-in functionality [JsonExtensionData].
The following converter allows for unknown properties to be deserialized into a typed container, rather than into an dictionary of arbitrary types:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonTypedExtensionDataAttribute : Attribute
{
}
public class TypedExtensionDataConverter<TObject> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TObject).IsAssignableFrom(objectType);
}
JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
{
try
{
return contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any()).Single();
}
catch (InvalidOperationException ex)
{
throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var extensionJsonProperty = GetExtensionJsonProperty(contract);
var extensionJProperty = (JProperty)null;
for (int i = jObj.Count - 1; i >= 0; i--)
{
var property = (JProperty)jObj.AsList()[i];
if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
{
if (extensionJProperty == null)
{
extensionJProperty = new JProperty(extensionJsonProperty.PropertyName, new JObject());
jObj.Add(extensionJProperty);
}
((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
}
}
var value = existingValue ?? contract.DefaultCreator();
using (var subReader = jObj.CreateReader())
serializer.Populate(subReader, value);
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var extensionJsonProperty = GetExtensionJsonProperty(contract);
JObject jObj;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
jObj = JObject.FromObject(value, serializer);
}
var extensionValue = (jObj[extensionJsonProperty.PropertyName] as JObject).RemoveFromLowestPossibleParent();
if (extensionValue != null)
{
for (int i = extensionValue.Count - 1; i >= 0; i--)
{
var property = (JProperty)extensionValue.AsList()[i];
jObj.Add(property.RemoveFromLowestPossibleParent());
}
}
jObj.WriteTo(writer);
}
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
public static class JsonExtensions
{
public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
public static IList<JToken> AsList(this IList<JToken> container) { return container; }
}
Then use it in your classes as follows:
class RootObject
{
[JsonProperty("users")]
public Users Users { get; set; }
}
[JsonConverter(typeof(TypedExtensionDataConverter<Users>))]
class Users
{
public Users()
{
this.UserTable = new Dictionary<string, User>();
}
[JsonProperty("parentname")]
public string ParentName { get; set; }
[JsonTypedExtensionData]
public Dictionary<string, User> UserTable { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
}
I wrote the converter in a fairly general way so it can be reused. A converter that is hardcoded for the Users type would require less code.
Your Json has to look like this:
{
"ParentName":"test",
"users":{
"10045":{
"name":"steve",
"state":"NY",
"id":"ebb2-92bf-3062-7774",
"ParentName":"someOtherName"
}
}
}
In order to deserialize it with your given class structure:
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Now you can deserialize the Json string with:
var root = JsonConvert.DeserializeObject<RootObject>(json);

Serialize objects implementing interface with System.Text.Json

I have a master class which contains a generic collection. Elements in the collection are of diffetent types, and each implements an interface.
Master class:
public class MasterClass
{
public ICollection<IElement> ElementCollection { get; set; }
}
Contract for the elements:
public interface IElement
{
string Key { get; set; }
}
Two samples for the elements:
public class ElementA : IElement
{
public string Key { get; set; }
public string AValue { get; set; }
}
public class ElementB : IElement
{
public string Key { get; set; }
public string BValue { get; set; }
}
I need to serialize an instance of MasterClass object using the new System.Text.Json library in Json. Using the following code,
public string Serialize(MasterClass masterClass)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
};
return JsonSerializer.Serialize(masterClass, options);
}
I get the follwing JSON:
{
"ElementCollection":
[
{
"Key": "myElementAKey1"
},
{
"Key": "myElementAKey2"
},
{
"Key": "myElementBKey1"
}
]
}
instead of:
{
"ElementCollection":
[
{
"Key": "myElementAKey1",
"AValue": "MyValueA-1"
},
{
"Key": "myElementAKey2",
"AValue": "MyValueA-2"
},
{
"Key": "myElementBKey1",
"AValue": "MyValueB-1"
}
]
}
Which class (converter, writer, ...)should I implement to obtain the complete JSON ?
Thanks in advance for your help.
This works for me:
public class TypeMappingConverter<TType, TImplementation> : JsonConverter<TType>
where TImplementation : TType
{
[return: MaybeNull]
public override TType Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
JsonSerializer.Deserialize<TImplementation>(ref reader, options);
public override void Write(
Utf8JsonWriter writer, TType value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, (TImplementation)value!, options);
}
Usage:
var options =
new JsonSerializerOptions
{
Converters =
{
new TypeMappingConverter<BaseType, ImplementationType>()
}
};
JsonSerializer.Deserialize<Wrapper>(value, options);
Tests:
[Fact]
public void Should_serialize_references()
{
// arrange
var inputEntity = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new TypeMappingConverter<IReference, Reference>()
}
};
var expectedOutput =
#"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
// act
var actualOutput = JsonSerializer.Serialize(inputEntity, options);
// assert
Assert.Equal(expectedOutput, actualOutput);
}
[Fact]
public void Should_deserialize_references()
{
// arrange
var inputJson =
#"{
""References"": [
{
""MyProperty"": ""abcd""
},
{
""MyProperty"": ""abcd""
}
]
}";
var expectedOutput = new Entity
{
References =
{
new Reference
{
MyProperty = "abcd"
},
new Reference
{
MyProperty = "abcd"
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.AddTypeMapping<IReference, Reference>();
// act
var actualOutput = JsonSerializer.Deserialize<Entity>(inputJson, options);
// assert
actualOutput
.Should()
.BeEquivalentTo(expectedOutput);
}
public class Entity
{
HashSet<IReference>? _References;
public ICollection<IReference> References
{
get => _References ??= new HashSet<IReference>();
set => _References = value?.ToHashSet();
}
}
public interface IReference
{
public string? MyProperty { get; set; }
}
public class Reference : IReference
{
public string? MyProperty { get; set; }
}
What you're looking for is called polymorphic serialization.
Here's Microsoft documentation article
Here's another question about it
According to documentation you just need to cast your interface to an object.
For example:
public class TreeRow
{
[JsonIgnore]
public ICell[] Groups { get; set; } = new ICell[0];
[JsonIgnore]
public ICell[] Aggregates { get; set; } = new ICell[0];
[JsonPropertyName("Groups")]
public object[] JsonGroups => Groups;
[JsonPropertyName("Aggregates")]
public object[] JsonAggregates => Aggregates;
public TreeRow[] Children { get; set; } = new TreeRow[0];
}
I have currently been infront of the same problem in Blazor app, so I was not been able to switch to Newtonsoft.Json easily. I found two ways. One is in reality hack. You can create custom converter, where you use Newtonsoft.Json in Read/Write methods, instead of System.Text.Json. But that was not what I was looking for. So I make some custom interface converter. I have some working solution, that not have been tested widely, but it's working for what I need.
Situation
I have a List<TInterface> with objects implementing TInterface. But there is a lot of different implementations. I need to serialize data on server, and deserialize on client WASM app, with all the data. For JavaScript deserialization, the implementation with custom Write method mentioned later is enough. For deserialization in C#, I need to know the exact types of objects serialized for each item in the list.
First, I need JsonConverterAttribute on interface. So I was following this article: https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json. There is some implementation of Writer, that will handle interface type. But there is not Read implementation. So I had to make my own.
How
modify Write method to write type of object as first property to JSON object. Using JsonDocument to get all properties from original object.
when reading the JSON, use clonned reader (as suggested in Microsoft docs for custom json converters) to find first property named $type with type information. Than create instance of that type and use type to deserialize data from original reader.
Code
Interface and classes:
[JsonInterfaceConverter(typeof(InterfaceConverter<ITest>))]
public interface ITest
{
int Id { get; set; }
string Name { get; set; }
}
public class ImageTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
}
public class TextTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
}
Interface converter attribute:
// Source: https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
Converter:
public class InterfaceConverter<T> : JsonConverter<T>
where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = readerClone.GetString();
if (propertyName != "$type")
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string typeValue = readerClone.GetString();
var instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, typeValue).Unwrap();
var entityType = instance.GetType();
var deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, (T)null, options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
Usage:
var list = new List<ITest>
{
new ImageTest { Id = 1, Name = "Image test", Image = "some.url.here" },
new TextTest { Id = 2, Name = "Text test", Text = "kasdglaskhdgl aksjdgl asd gasdg", IsEnabled = true },
new TextTest { Id = 3, Name = "Text test 2", Text = "asd gasdg", IsEnabled = false },
new ImageTest { Id = 4, Name = "Second image", Image = "diff.url.here" }
};
var json = JsonSerializer.Serialize(list);
var data = JsonSerializer.Deserialize<List<ITest>>(json);
// JSON data
// [
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":1,
// "Name":"Image test",
// "Image":"some.url.here"
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":2,
// "Name":"Text test",
// "Text":"kasdglaskhdgl aksjdgl asd gasdg",
// "IsEnabled":true
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":3,
// "Name":"Text test 2",
// "Text":"asd gasdg",
// "IsEnabled":false
// },
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":4,
// "Name":"Second image",
// "Image":"diff.url.here"
// }
// ]
Edit:
I made a NuGet package with this logic. You can download it here: InterfaceConverter.SystemTextJson
Edit 26.3.2022:
The NuGet package version has implemented more logic, eg. looking for type in all referenced assemblies.
The solution is to implement a generic converter (System.Text.Json.Serialization.JsonConverter) :
public class ElementConverter : JsonConverter<IElement>
{
public override IElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, IElement value, JsonSerializerOptions options)
{
if (value is ElementA)
JsonSerializer.Serialize(writer, value as ElementA, typeof(ElementA), options);
else if (value is ElementB)
JsonSerializer.Serialize(writer, value as ElementB, typeof(ElementB), options);
else
throw new ArgumentOutOfRangeException(nameof(value), $"Unknown implementation of the interface {nameof(IElement)} for the parameter {nameof(value)}. Unknown implementation: {value?.GetType().Name}");
}
}
This just needs some more work for the Read method.
I have had the same issue, but my problem may not have been related to yours. It turns out that each object to which the incomming JSON data must be serialized requires a constructor with no arguments. All my objects had constructors with all arguments (to make it easier to create and populate them from a database).
Improved On #t00thy Solution
Your solution is nice but what if concrete type in an other assembly?
Converter Class
public class InterfaceConverter<T> : JsonConverter<T> where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
throw new JsonException("Problem in Start object! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Token Type not equal to property name! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? propertyName = readerClone.GetString();
if (string.IsNullOrWhiteSpace(propertyName) || propertyName != "$type")
throw new JsonException("Unable to get $type! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
throw new JsonException("Token Type is not JsonTokenString! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? typeValue = readerClone.GetString();
if(string.IsNullOrWhiteSpace(typeValue))
throw new JsonException("typeValue is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
string? asmbFullName = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(ass => !string.IsNullOrEmpty(ass.GetName().Name) && ass.GetName().Name.Equals(typeValue.Split(" ")[1]))?.FullName;
if (string.IsNullOrWhiteSpace(asmbFullName))
throw new JsonException("Assembly name is null or empty string! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
ObjectHandle? instance = Activator.CreateInstance(asmbFullName, typeValue.Split(" ")[0]);
if(instance == null)
throw new JsonException("Unable to create object handler! Handler is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? unwrapedInstance = instance.Unwrap();
if(unwrapedInstance == null)
throw new JsonException("Unable to unwrap instance! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
Type? entityType = unwrapedInstance.GetType();
if(entityType == null)
throw new JsonException("Instance type is null! Or instance is null! method: " + nameof(Read) + " class :" + nameof(InterfaceConverter<T>));
object? deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
if(deserialized == null)
throw new JsonException("De-Serialized object is null here!");
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, typeof(T) ,options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName + " " + type.Assembly.GetName().Name);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
Converter Attribute
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
Interfaces and Classes
[JsonInterfaceConverter(typeof(InterfaceConverter<IUser>))]
public interface IUser
{
int Id { get; set; }
string Name { get; set; }
IEnumerable<IRight> Rights { get; set; }
}
[JsonInterfaceConverter(typeof(InterfaceConverter<IRight>))]
public interface IRight
{
int Id { get; set; }
bool HasRight { get; set; }
}
public class User : IUser
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public IEnumerable<IRight> Rights { get; set; } = Enumerable.Empty<IRight>();
}
public class Right : IRight
{
public int Id { get; set; }
public bool HasRight { get; set; }
}
Usage:
// your dependency injector
IUser user = IServiceProvider.GetRequiredService<IUser>();
user.Id = 1;
user.Name = "Xyz";
List<IRight> rights = new ();
// your dependency injector
IRight right1 = IServiceProvider.GetRequiredService<IRight>();
right1.Id = 1;
right1.HasRight = true;
rights.Add(right1);
// your dependency injector
IRight right2 = IServiceProvider.GetRequiredService<IRight>();
right2.Id = 2;
right2.HasRight = true;
rights.Add(right2);
// your dependency injector
IRight right3 = IServiceProvider.GetRequiredService<IRight>();
right3.Id = 1;
right3.HasRight = true;
rights.Add(right2);
var serializedRights = JsonSerializer.Serialize(rights);
user.Rights = rights;
// Serialization is simple
var serilizedUser = JsonSerializer.Serialize(user);
//But for DeSerialization of single object you need to use it some thing like this
// Ask your dependency injector to resolve and get type of object
IUser usr = JsonSerializer.Deserialize(serilizedUser, IServiceProvider.GetRequiredService<IUser>().GetType());
//DeSerialization of list or enumerable is simple
IEnumerable<IRight>? rits = JsonSerializer.Deserialize<IEnumerable<IRight>>(serializedRights);

Json.Net setting property name as numbers

Is there a short, more clean way to achieve this ?
public class Sidebar
{
[JsonProperty("0")]
public string Hurry = "hurry";
[JsonProperty("1")]
public string Dont_Spam_This_Button = "don't spam this button";
[JsonProperty("2")]
public string Navigation = "navigation";
[JsonProperty("3")]
public string Overview = "overview";
i want to objects to be numbered so is there a way to do that programatically instead of using attributes and manually counting ?
Yes, you can get the result you want using a custom JsonConverter like this one:
class NumberedPropertiesConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
int count = 0;
foreach (MemberInfo member in value.GetType().GetMembers())
{
object memberVal = null;
if (member.MemberType == MemberTypes.Field)
{
memberVal = ((FieldInfo)member).GetValue(value);
}
else if (member.MemberType == MemberTypes.Property)
{
memberVal = ((PropertyInfo)member).GetValue(value);
}
else
{
continue;
}
JToken token = memberVal != null ? JToken.FromObject(memberVal, serializer) : null;
jo.Add(count.ToString(), token);
count++;
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, just mark your class with a [JsonConverter] attribute specifying the type of the custom converter:
[JsonConverter(typeof(NumberedPropertiesConverter))]
public class Sidebar
{
...
}
One important note: the properties/fields will be numbered according to the order returned by the Type.GetMembers() method. Generally, this will match the order declared in the class; however, if you have a mix of public properties and public fields, then all of the properties will be returned before all of the fields.
Here is a demo:
public class Program
{
public static void Main()
{
Sidebar sb = new Sidebar();
string json = JsonConvert.SerializeObject(sb, Formatting.Indented);
Console.WriteLine(json);
}
}
[JsonConverter(typeof(NumberedPropertiesConverter))]
public class Sidebar
{
public string Foo { get { return "foo property"; } }
public string Hurry = "hurry";
public string Dont_Spam_This_Button = "don't spam this button";
public string Navigation = "navigation";
public string Overview = "overview";
public string Bar { get { return "bar property"; } }
}
Output:
{
"0": "foo property",
"1": "bar property",
"2": "hurry",
"3": "don't spam this button",
"4": "navigation",
"5": "overview"
}
Fiddle: https://dotnetfiddle.net/aZ51qv
What you are doing will give you something like {"0":"hurry",...,"3":"overview"} when what you want is more like ["hurry",...,"overview"]
What you can do, is create a custom converter/serializer, or if you don't need automatic serialization/deserialization in .net, just serialize an object[] array in a class method...
public string AsJson()
{
return JsonConvert.SerializeObject(new object[] {
Hurry, Dont_Spam_This_Button, Navigation, Overview
});
}