I have a fairly generic 'rule' class that I am using to drive the behavior of an analysis engine I'm writing:
public class Rule
{
/// <summary>
/// The general rule type.
/// </summary>
public RuleType RuleType { get; set; }
/// <summary>
/// The human-readable description of the rule.
/// </summary>
public string RuleDescription { get; set; }
/// <summary>
/// The integer magnitude of the rule, if applicable.
/// </summary>
public int? RuleInt { get; set; }
/// <summary>
/// The boolean sign associated with the rule, if applicable.
/// </summary>
public bool? RuleBool { get; set; }
/// <summary>
/// The enum flag associated with the rule, if applicable. CAN be null.
/// </summary>
public System.Enum RuleFlagEnum { get; set; }
/// <summary>
/// A dumping ground for any other random crap I've failed to account for at this point in time.
/// </summary>
public object RuleObject { get; set; }
}
RuleType is a specific enum, like so:
public enum RuleType
{
Invalid,
ModifyDifficulty,
StrengthChange,
ColorChange,
SignChange
}
Using Json.NET, that both serializes and deserializes just fine.
RuleEnum, however, is giving me problems. Whether using the default enum serialization or the string enum serialization, the specific type of enum is not provided. As such, during deserialization, I am left with System.Enum and a string value, which is wholly unhelpful.
This is an example of the serialization, to show what I'm talking about:
{
"RuleType": "SignChange",
"RuleDescription": "Strength 1 Inversion Gate",
"RuleInt": 1,
"RuleFlagEnum": "Negative"
}
RuleFlagEnum, in this case, is referring to the enum:
public enum SignChange
{
Zero,
Positive,
Negative
}
I have tried using all of the TypeNameHandling options inside Json.NET. They only put type hinting on the objects, which doesn't help with RuleFlagEnum since it is technically a primitive.
I would really, really like to keep the enum at System.Enum so we can load any arbitrary enum in for later interpretation by the rule type, so the entire thing is more expandable. Is this possible?
The difficulty here is that System.Enum is an abstract class, so it is impossible to deserialize a value of unknown concrete type as such a type. Rather, one needs to have the specific type information in the JSON somewhere, however Json.NET will serialize an enum as a string or an integer (depending upon whether a StringEnumConverter is applied) -- but not an as an object, thus leaving no opportunity for a polymorphic "$type" property to be added.
The solution is, when serializing, to serialize a generic wrapper class that can convey the concrete type information:
public abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then use serialize the wrapper when serializing your class:
/// <summary>
/// The enum flag associated with the rule, if applicable. CAN be null.
/// </summary>
[JsonIgnore]
public System.Enum RuleFlagEnum { get; set; }
[JsonProperty("RuleFlagEnum", TypeNameHandling = TypeNameHandling.All)]
TypeWrapper RuleFlagEnumValue
{
get
{
return RuleFlagEnum == null ? null : TypeWrapper.CreateWrapper(RuleFlagEnum);
}
set
{
if (value == null || value.ObjectValue == null)
RuleFlagEnum = null;
else
RuleFlagEnum = (Enum)value.ObjectValue;
}
}
This produces JSON like the following:
{
"RuleType": "ModifyDifficulty",
"RuleFlagEnum": {
"$type": "Question31351262.TypeWrapper`1[[Question31351262.MyEnum, MyApp]], MyApp",
"Value": "Two, Three"
},
}
Related
I have a class DocumentObject that extends DynamicObject to allow dynamic membership attributes.
public class DocumentObject : DynamicObject
{
/// <summary>
/// Inner dictionary that holds the dynamic members of the object
/// </summary>
Dictionary<string, object> dictionary = new Dictionary<string, object>();
/// <summary>
/// Try to get the member that is not defined in the class (additional dynamic members) from inner dictionary
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
/// <summary>
/// Try to set the member that is not defined in the class (additional dynamic members) to inner dictionary
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
/// <summary>
/// Get the names of all the dynamic members
/// </summary>
/// <returns></returns>
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys;
}
}
I have a base Person class that inherits DocumentObject
public class PersonDto : DocumentObject
{
[JsonProperty("id")]
public string Id { get; set; }
}
Another child OfficePersonDto class that inherits PersonDto
public class OfficePersonDto : PersonDto
{
[JsonProperty("name")]
public string Name { get; set; }
}
In my function, I am receiving JSON object that has to be at least PersonDto object, but if its an OfficePersonDto type, I wish to be able to cast PersonDto into OfficePersonDto.
I.e. JSON = {"Id":1, "Name": "Orchard"}, in PersonDto, name attribute will be saved using DocumentObject's dictionary, while casting to OfficePersonDto, both Id and name are attributes of the class.
How can I cast from PersonDto to a child class e.g. OfficePersonDto?
PersonDto personDto = ...
OfficePersonDto off = personDto as OfficePersonDto // results in null or Name is null
Automapper is very helpful when the issue is about these kind of conversions.
I'll show you a quick working example. But it will be still open for more refactoring of course.
PersonDto personDto = ...
// Do not use the following conversion, instead get help from automapper
// OfficePersonDto off = personDto as OfficePersonDto
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDto, OfficePersonDto>()
.ForMember(d => d.Name, opt => opt.MapFrom(new OfficePersonNameResolver()));
});
var mapper = config.CreateMapper();
var off = mapper.Map<OfficePersonDto>(personDto); // off is created with correct values
OfficePersonNameResolver is like this:
public class OfficePersonNameResolver : IValueResolver<PersonDto, OfficePersonDto, string>
{
public string Resolve(PersonDto source, OfficePersonDto destination, string destMember, ResolutionContext context)
{
return (source as dynamic).Name;
}
}
You are welcome to ask if you have questions about this.
Edit: Generalizing the resolver to IValueResolver<TSource, TDestination, TDestinationMember>
Change the creation of configuration like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDto, OfficePersonDto>()
.ForMember(d => d.Name, opt => opt.MapFrom(new DynamicObjectValueResolver<PersonDto, OfficePersonDto, string>("name")));
});
And The value resolver should be like this now:
public class DynamicObjectValueResolver<TSource, TDestination, TDestinationMember> : IValueResolver<TSource, TDestination, TDestinationMember>
where TDestinationMember : class
{
private readonly string _propertyName;
public DynamicObjectValueResolver(string propertyName)
{
_propertyName = propertyName;
}
public TDestinationMember Resolve(TSource source, TDestination destination, TDestinationMember destMember, ResolutionContext context)
{
dynamic eo = JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(source));
IDictionary<string, object> dictionary = eo;
return dictionary[_propertyName] as TDestinationMember;
}
}
Working example: https://dotnetfiddle.net/KS91To
I want to get a list of fields with an Attribute Sync.Field on each of the field in the class. The field can / cannot have the attribute of Sync.Field
I have been trying the following, but having trouble getting the custom attribute for each field.
FieldInfo[] fiClass = typClass.GetFields();
FieldInfo[] lst = fiClass
.Where(c => c.CustomAttribute().GetType() == typeOf(Sync.Field))
.ToList();
I have a generic collection class, which uses a data class to match an SNMP table with data class fields. Like JsonProperty matches deserialised values to properties. In the same way I define a SNMPPropertyAttribute. The attribute itself is
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class SNMPPropertyAttribute : Attribute
{
public SNMPPropertyAttribute(string propertyOID) => PropertyOID = new ObjectIdentifier(propertyOID);
public ObjectIdentifier PropertyOID { get; }
}
When in the table constructor, I'm making a dictionary of data fiels and their OIDs from the attribute:
public SNMPTableEntity()
{
snmpPoperties = new Dictionary<ObjectIdentifier, PropertyInfo>();
foreach (PropertyInfo myProperty in GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
CustomAttributeData snmpAttribure = myProperty.CustomAttributes.Where(x => x.AttributeType == typeof(SNMPPropertyAttribute)).FirstOrDefault();
if (snmpAttribure != null)
snmpPoperties.Add(new ObjectIdentifier((string)snmpAttribure.ConstructorArguments[0].Value), myProperty);
}
}
It looks similar to what are you trying to acheive, so hopefully it helps. But the difference, is that I'm using properties, not fields. Not sure if it makes a big difference, but...
There is an example of using:
public class InterfaceTableEntity : SNMPTableEntity
{
/// <summary>
/// A unique value for each interface. Its value ranges between 1 and the value of ifNumber. The value for each interface must remain constant at least from one re-initialization of the entity's network management system to the next re- initialization.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.1")]
protected Integer32 ifIndex { get; set; }
/// <summary>
/// A textual string containing information about the interface. This string should include the name of the manufacturer, the product name and the version of the hardware interface.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.2")]
protected OctetString ifDescr { get; set; }
/// <summary>
/// The type of interface, distinguished according to the physical/link protocol(s) immediately `below' the network layer in the protocol stack.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.3")]
protected Integer32 ifType { get; set; }
}
If you have the FieldInfo, you can get an instance of its attribute using this code:
var attr = fieldInfo.GetCustomAttributes().OfType<Sync.FieldAttribute>().SingleOrDefault();
See my example on DotNetFiddle.
I want to create a xml for a generic class. One of the properties has the generic type. For this property I don't want to use the property name as its XML element name, but the name of the generic type.
The class looks like this:
[XmlRoot("Entity")]
public class StoreItem<TEntity>
where TEntity : class, new()
{
/// <summary>
/// Gets and sets the status of the entity when storing.
/// </summary>
[XmlAttribute]
public System.Data.Services.Client.EntityStates Status { get; set; }
/// <summary>
/// Gets and sets the entity to be stored.
/// </summary>
public TEntity Entity { get; set; }
}
When serializing a store item of kind StoreItem<SewageArea> the XML should contain something like:
<Entity Status="Deleted">
<SewageArea ...>
...
</SewageArea>
<Entity>
The requirement is, that the SewageArea in the above example should be serialized in the "normal" way. Another important thing is that if its possible the code should be prepared to automatically serializes new added properties at the StoreItemclass.
You'd like to do something along the lines of Rename class when serializing to XML but you cannot because attribute arguments cannot contain generic type parameters, i.e. [XmlElement(typeof(TEntity))]. And the obvious alternative of implementing IXmlSerializable is inconvenient because you lose automatic serialization of properties subsequently added to StoreItem<TEntity>.
Instead, what you can do is to make use of an [XmlAnyElement] surrogate property to do a nested serialization of your TEntity, as follows:
[XmlRoot("Entity")]
public class StoreItem<TEntity>
where TEntity : class, new()
{
/// <summary>
/// Gets and sets the status of the entity when storing.
/// </summary>
[XmlAttribute]
public System.Data.Services.Client.EntityStates Status { get; set; }
/// <summary>
/// Gets and sets the entity to be stored.
/// </summary>
[XmlIgnore]
public TEntity Entity { get; set; }
[XmlAnyElement]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XElement XmlEntity
{
get
{
return (Entity == null ? null : XObjectExtensions.SerializeToXElement(Entity, null, true));
}
set
{
Entity = (value == null ? null : XObjectExtensions.Deserialize<TEntity>(value));
}
}
}
Using the extension methods:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(null);
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
serializer = serializer ?? new XmlSerializer(typeof(T));
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement SerializeToXElement<T>(this T obj)
{
return obj.SerializeToXElement(null, true);
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer = serializer ?? new XmlSerializer(obj.GetType());
serializer.Serialize(writer, obj, ns);
}
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
}
Note that the [XmlAnyElement] property will be called for all unknown elements, so if your XML for some reason has unexpected elements, you may get an exception thrown from XObjectExtensions.Deserialize<TEntity>(value)) because the root element name is wrong. You may want to catch and ignore exceptions from this method if that is a possibility.
Then, for the sample TEntity class
public class SewageArea
{
public double Area { get; set; }
}
The XML output is:
<Entity Status="State1">
<SewageArea>
<Area>10101</Area>
</SewageArea>
</Entity>
Sample fiddle.
I'm getting a "type is interface, cannot be instanciated" deserialization error with json.net even though I am specifying type on the object I'm trying to deserialize
private static JsonSerializerSettings settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto };
/// <summary>
/// Returns an object created from the jObject and placed in a stub object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jObj"></param>
/// <returns></returns>
public static T FromJObject<T>(JToken jObj)
{
if (jObj == null)
{
jObj = new JObject();
}
if (jObj is JValue)
{
return (T)((JValue)jObj).Value;
}
else
{
return (T)JsonConvert.DeserializeObject<T>(jObj.ToString(), settings);
}
}
this is jObj
{
"Id": 2,
"Name": "Name",
"JsonMapEnum": 0,
"Validations": [
{
"Id": 1,
"$type": "JsonMap.Default.JValidation, JsonMap"
}
],
"JSType": 3,
"SubJsonMapEnum": -1,
"$type": "JsonMap.Default.JAttribute, JsonMap"
}
This is the error
Could not create an instance of type JsonMap.Interfaces.IValidation. Type is an interface or abstract class and cannot be instantated. Path 'Validations[0].Id'
It looks like it's trying to turn Id into a Validation object. Why?
these are the interfaces implemented by my types
public interface IJsonMap
{
long Id { get; set; }
String Name { get; set; }
LazyEnum JsonMapEnum { get; set; }
}
public interface IAttribute : IJsonMap
{
IEnumerable<IValidation> Validations { get; set; }
LazyEnum JSType { get; set; }
LazyEnum SubJsonMapEnum { get; set; }
}
public interface IValidation : IJsonMap
{
IEnumerable<IArgument> Arguments { get; set; }
}
this is the call
FromJObject<JAttribute>(CreationJObj)
JAttribute implements IAttribute
Apparently, "$type" has to be the first property in the String literal in order for the type to be caught by the type name handler. I imagine this is because The deserializer isn't checking for the existence of $type, it simply uses the json string reader, and upon finding the first property without the type being set, it fails out.
Here's the method I created to ensure $type is always first
private static JToken ReorderJToken(this JToken jTok)
{
if (jTok is JArray)
{
var jArr = new JArray();
foreach (var token in jTok as JArray)
{
jArr.Add(token.ReorderJToken());
}
return jArr;
}
else if( jTok is JObject)
{
var jObj = new JObject();
foreach(var prop in (jTok as JObject).Properties().OrderBy(x=> x.Name))
{
prop.Value = prop.Value.ReorderJToken();
jObj.Add(prop);
}
return jObj;
}
return jTok;
}
Well clearly it cannot infer the proper type based off the arguments you're feeding it. Without more context of how it's called I can't make what you have work. However, if you call it with an object cast to the interfaces type it will not work. With the definition you have FromObject must always be called with T is the type of a class. First thing to do is make sure you never try to call the method where T is equal to typeof(IOneOfMyInterfaces) because this is guaranteed to fail. If that doesn't fix it I would try calling a different version of the deserialize method;
IMyInterface obj = (IMyInterface)serializer.Deserialize(validatingReader, t);
This works where validatingReader is a JsonReader and t is the Type of the class object implementing IMyInterface. I'm currently using it for generic deserialization with json schemas for validating json correctness.
Here is my simple User POCO class:
/// <summary>
/// The User class represents a Coderwall User.
/// </summary>
public class User
{
/// <summary>
/// A User's username. eg: "sergiotapia, mrkibbles, matumbo"
/// </summary>
public string Username { get; set; }
/// <summary>
/// A User's name. eg: "Sergio Tapia, John Cosack, Lucy McMillan"
/// </summary>
public string Name { get; set; }
/// <summary>
/// A User's location. eh: "Bolivia, USA, France, Italy"
/// </summary>
public string Location { get; set; }
public int Endorsements { get; set; } //Todo.
public string Team { get; set; } //Todo.
/// <summary>
/// A collection of the User's linked accounts.
/// </summary>
public List<Account> Accounts { get; set; }
/// <summary>
/// A collection of the User's awarded badges.
/// </summary>
public List<Badge> Badges { get; set; }
}
And the method I'm using to deserialize a JSON response into a User object (this actual JSON call is here):
private User LoadUserFromJson(string response)
{
var outObject = JsonConvert.DeserializeObject<User>(response);
return outObject;
}
This fires an exception:
Cannot deserialize the current JSON object (e.g. {"name":"value"})
into type
'System.Collections.Generic.List`1[CoderwallDotNet.Api.Models.Account]'
because the type requires a JSON array (e.g. [1,2,3]) to deserialize
correctly.
To fix this error either change the JSON to a JSON array
(e.g. [1,2,3]) or change the deserialized type so that it is a normal
.NET type (e.g. not a primitive type like integer, not a collection
type like an array or List) that can be deserialized from a JSON
object. JsonObjectAttribute can also be added to the type to force it
to deserialize from a JSON object. Path 'accounts.github', line 1,
position 129.
Having never worked with this DeserializeObject method before, I'm kind of stuck here.
I've made sure that the property names in the POCO class are the same as the names in the JSON response.
What can I try to deserialize JSON into this POCO class?
Here is a working example.
Keypoints are:
Declaration of Accounts
Use of JsonProperty attribute
.
using (WebClient wc = new WebClient())
{
var json = wc.DownloadString("http://coderwall.com/mdeiters.json");
var user = JsonConvert.DeserializeObject<User>(json);
}
-
public class User
{
/// <summary>
/// A User's username. eg: "sergiotapia, mrkibbles, matumbo"
/// </summary>
[JsonProperty("username")]
public string Username { get; set; }
/// <summary>
/// A User's name. eg: "Sergio Tapia, John Cosack, Lucy McMillan"
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// A User's location. eh: "Bolivia, USA, France, Italy"
/// </summary>
[JsonProperty("location")]
public string Location { get; set; }
[JsonProperty("endorsements")]
public int Endorsements { get; set; } //Todo.
[JsonProperty("team")]
public string Team { get; set; } //Todo.
/// <summary>
/// A collection of the User's linked accounts.
/// </summary>
[JsonProperty("accounts")]
public Account Accounts { get; set; }
/// <summary>
/// A collection of the User's awarded badges.
/// </summary>
[JsonProperty("badges")]
public List<Badge> Badges { get; set; }
}
public class Account
{
public string github;
}
public class Badge
{
[JsonProperty("name")]
public string Name;
[JsonProperty("description")]
public string Description;
[JsonProperty("created")]
public string Created;
[JsonProperty("badge")]
public string BadgeUrl;
}
Another, and more streamlined, approach to deserializing a camel-cased JSON string to a pascal-cased POCO object is to use the CamelCasePropertyNamesContractResolver.
It's part of the Newtonsoft.Json.Serialization namespace. This approach assumes that the only difference between the JSON object and the POCO lies in the casing of the property names. If the property names are spelled differently, then you'll need to resort to using JsonProperty attributes to map property names.
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
. . .
private User LoadUserFromJson(string response)
{
JsonSerializerSettings serSettings = new JsonSerializerSettings();
serSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
User outObject = JsonConvert.DeserializeObject<User>(jsonValue, serSettings);
return outObject;
}
The accounts property is defined like this:
"accounts":{"github":"sergiotapia"}
Your POCO states this:
public List<Account> Accounts { get; set; }
Try using this Json:
"accounts":[{"github":"sergiotapia"}]
An array of items (which is going to be mapped to the list) is always enclosed in square brackets.
Edit: The Account Poco will be something like this:
class Account {
public string github { get; set; }
}
and maybe other properties.
Edit 2: To not have an array use the property as follows:
public Account Accounts { get; set; }
with something like the sample class I've posted in the first edit.
You could create a JsonConverter. See here for an example thats similar to your question.
Along the lines of the accepted answer, if you have a JSON text sample you can plug it in to this converter, select your options and generate the C# code.
If you don't know the type at runtime, this topic looks like it would fit.
dynamically deserialize json into any object passed in. c#
to fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the
deserialized type so that it is a normal .NET type (e.g. not a primitive type like
integer, not a collection type like an array or List) that can be deserialized from a
JSON object.`
The whole message indicates that it is possible to serialize to a List object, but the input must be a JSON list.
This means that your JSON must contain
"accounts" : [{<AccountObjectData}, {<AccountObjectData>}...],
Where AccountObject data is JSON representing your Account object or your Badge object
What it seems to be getting currently is
"accounts":{"github":"sergiotapia"}
Where accounts is a JSON object (denoted by curly braces), not an array of JSON objects (arrays are denoted by brackets), which is what you want. Try
"accounts" : [{"github":"sergiotapia"}]
That's not exactly what I had in mind. What do you do if you have a generic type to only be known at runtime?
public MyDTO toObject() {
try {
var methodInfo = MethodBase.GetCurrentMethod();
if (methodInfo.DeclaringType != null) {
var fullName = methodInfo.DeclaringType.FullName + "." + this.dtoName;
Type type = Type.GetType(fullName);
if (type != null) {
var obj = JsonConvert.DeserializeObject(payload);
//var obj = JsonConvert.DeserializeObject<type.MemberType.GetType()>(payload); // <--- type ?????
...
}
}
// Example for java.. Convert this to C#
return JSONUtil.fromJSON(payload, Class.forName(dtoName, false, getClass().getClassLoader()));
} catch (Exception ex) {
throw new ReflectInsightException(MethodBase.GetCurrentMethod().Name, ex);
}
}
May be late but using QuickType is the easiest way to do that:
https://app.quicktype.io/
For anyone having this problem i was not seeing the json value properly. https://jsonutils.com/ there you can check the classes that should be generated and return ONLY ONE of those classes once you read the json in your code.
For example i needed a booklist object so my code should only read one
res = await response.Content.ReadAsAsync<BookList>();
Where booklist looks something like
public class BookList
{
[JsonProperty("data")]
public IList<Datum> Data { get; set; }
}
And in that list have smaller book clasess that the converter named Datum (just books)
public class Datum
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("isbn")]
public string Isbn { get; set; }
}
Again, if you have doubts https://jsonutils.com/