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

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);

Related

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);

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);

Web API - JSON serialize properties as array of strings

I have a class
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
I'd like to serialize it into:
{ "Person": "[John, Smith, 13]" }
instead of
{"Name":"John", "LastName":"Smith", "Age": 13}
Is it possible to achieve this without writing a custom serializer? If not, how do I apply a custom serializer only to a specific class?
Since the json string you want to get when serializing an instance of Person is not the standard one, you'll need to write a custom converter and decorate your class with it. One way to do that is the following. Write class that inherits from JsonConverter:
public class CustomConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null) return;
writer.WriteStartObject();
writer.WritePropertyName(value.GetType().Name);
writer.WriteStartArray();
var properties = value.GetType().GetProperties();
foreach (var property in properties)
writer.WriteValue(value.GetType().GetProperty(property.Name).GetValue(value));
writer.WriteEndArray();
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (Person);
}
}
Decorate your class with the JsonConverter attribute:
[JsonConverter(typeof(CustomConverter))]
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Now lets say that you use the following code:
var person = new Person
{
Name = "John",
LastName = "Smith",
Age = 13
};
var json = JsonConvert.SerializeObject(person, Formatting.Indented);
The json varible wil have this value:
{
"Person": [
"John",
"Smith",
13
]
}
Demo here

How to deserialize a child list of interfaces?

I'm using ASP.NET MVC2 and I have the following object structure:
public class IDealer {
string Name { get; set; }
List<IVehicle> Vehicles { get; set; }
}
public class DealerImpl {
public string Name { get; set; }
public List<IVehicle> Vehicles { get; set; }
}
public interface IVehicle {
string Type { get; }
}
public class Car : IVehicle {
public string Type { get { return this.GetType().FullName; } }
}
public class Truck : IVehicle {
public string Type { get { return this.GetType().FullName; } }
}
I have the following class as my ModelBinder which deserializes objects in my page requests:
public class JsonModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
return deserialize(controllerContext, bindingContext);
}
protected static bool IsJSONRequest(ControllerContext controllerContext) {
var contentType = controllerContext.HttpContext.Request.ContentType;
return contentType.Contains("application/json");
}
protected virtual object deserialize(ControllerContext controllerContext, ModelBindingContext bindingContext) {
Type modelType = bindingContext.ModelMetadata.ModelType;
bool isNotConcrete = bindingContext.ModelMetadata.ModelType.IsInterface || bindingContext.ModelMetadata.ModelType.IsAbstract;
if (!IsJSONRequest(controllerContext)) {
return base.BindModel(controllerContext, bindingContext);
} else {
var request = controllerContext.HttpContext.Request;
var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();
if (isNotConcrete) {
Dictionary<string, Object> result = JsonConvert.DeserializeObject<Dictionary<string, Object>>(jsonStringData);
string type = result["Type"] as string;
modelType = Type.GetType(type + ",MyCompany.Common");
}
return JsonConvert.DeserializeObject(jsonStringData, modelType, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
}
}
}
// ASP.NET MVC Controller
protected override void Initialize(System.Web.Routing.RequestContext requestContext) {
base.Initialize(requestContext);
ModelBinders.Binders.DefaultBinder = new JsonModelBinder();
}
[HttpPost]
public ActionResult addUpdateDealer(IDealer dealer) {
// breaks before here with the error in the comment below
}
// and in the aspx page
<script>
var model = <%= JsonConvert.SerializeObject(Model, Formatting.None, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }) %>;
</script>
The problem I'm running into, is that when the code tries to deserialize the child list of IVehicles, it does not know which type of vehicle to instantiate. I put a property on IVehicle called "Type" which could be used to help determine which class to instantiate, but I'm not sure what/where/how to provide an override to perform this check.
Your solution is similar to what JSON.NET has built-in now, called TypeNameHandling. Here are the release notes on that.
Your JSON message will need to include a $type property, which won't be deserialized, but will be interpreted by the deserializer as the concrete type to use.

JSON object setting to default /null value [duplicate]

I'm using Json.NET for a project I'm working on.
From an external API, I am receiving JSON with properties that are objects, but when they are empty 'false' is passed.
For example:
data: {
supplier: {
id: 15,
name: 'TheOne'
}
}
Could also be:
data: {
supplier: false
}
How should I define the supplier property so that the supplier will be deserialized to a Supplier object or null.
Right now I have:
public class Data {
[JsonProperty("supplier")]
public SupplierData Supplier { get; set; }
}
public class SupplierData {
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
But now when trying to deserialize when supplier has a value of 'false' it fails.
I would like the Supplier property to be null when the JSON value is 'false'.
I hope someone knows how to do this. Thanks.
This can be solved by making a custom JsonConverter for your SupplierData class. Here is what the converter might look like:
class SupplierDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(SupplierData));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject<SupplierData>();
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
To use it, all you would need to do is add a [JsonConverter] attribute to the Supplier property in your Data class like this:
public class Data
{
[JsonProperty("supplier")]
[JsonConverter(typeof(SupplierDataConverter))]
public SupplierData Supplier { get; set; }
}
Below is a demonstration of the converter in action. Note that the demo assumes you have some kind of containing object for the data property, since the JSON in your question can't stand on its own. I defined a class called RootObject for this purpose:
public class RootObject
{
[JsonProperty("data")]
public Data Data { get; set; }
}
The actual demo code follows:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""data"":
{
""supplier"":
{
""id"": 15,
""name"": ""TheOne""
}
}
}";
Console.WriteLine("--- first run ---");
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
DumpSupplier(obj.Data.Supplier);
json = #"
{
""data"":
{
""supplier"": false
}
}";
Console.WriteLine("--- second run ---");
obj = JsonConvert.DeserializeObject<RootObject>(json);
DumpSupplier(obj.Data.Supplier);
}
static void DumpSupplier(SupplierData supplier)
{
if (supplier != null)
{
Console.WriteLine("Id: " + supplier.Id);
Console.WriteLine("Name: " + supplier.Name);
}
else
{
Console.WriteLine("(null)");
}
Console.WriteLine();
}
}
And here is the output from the above:
--- first run ---
Id: 15
Name: TheOne
--- second run ---
(null)