I have a class with the following fields.
public class Payment
{
public string type { get; set; }
public string issuer { get; set; }
public string identifier { get; set; }
public float price { get; set; }
}
The "type" field can be "Giftcard" or "Creditcard".
I want to serialize it depends on the "type" field.
{
"type": "Giftcard",
"giftcard_no": "111111111111",
"giftcard_price": 100
}
{
"type": "Creditcard",
"issuer": "AMEX",
"last_4_digits": "1000",
"creditcard_price": 100
}
As you can see, the field names are different depends on the "type" field.
And "issuer" field is ignored in case of Giftcard.
I've found similar questions, but couldn't find the correct answer.
I will be appreciated for any help.
Thank you.
It sounds to me like what you want is different subclasses, using type to determine which one to use when deserializing. I've found that the JsonSubTypes package (GitHub repo) works very well for this.
You'd have something like this:
[JsonConverter(typeof(JsonSubtypes), "type")]
[JsonSubtypes.KnownSubType(typeof(GiftCard), "Giftcard")]
[JsonSubtypes.KnownSubType(typeof(CreditCard), "Creditcard")]
public class Payment
{
[JsonProperty("type")]
public virtual string Type { get; }
}
public class CreditCard : Payment
{
public override string Type => "Creditcard";
[JsonProperty("issuer")
public string Issuer { get; set; }
// Etc, other properties
}
public class GiftCard : Payment
{
public override string Type => "Giftcard";
[JsonProperty("giftcard_no")]
public string GiftCardNumber { get; set; }
// Etc, other properties
}
There are various different options for exactly how you register things - the README on the GitHub repo gives concrete examples, which is really useful.
You'd then deserialize to Payment, but the returned value would be a reference to an instance of GiftCard or CreditCard.
Related
So, I'm not sure where my head is at today but I can't wrap my head around a decent solution to this issue and I was hoping you guys can help.
We are building a .NET Core / EF Core / SQL application that hands a base record type, and then we layer on metadata objects on top of that base record to enrich the datasets. So my base class looks something like this:
public class Record
{
[NotNull]
public Guid Id { get; set }
[CanBeNull]
public virtual string InvoiceNum { get; set; }
[CanBeNull]
public virtual string Notes { get; set; }
//Enum that defines the extra properties
public virtual RecordType RecordType { get; set; }
public Dictionary<string,object> ExtraProperties { get; set; }
}
and my MetaData class looks something like this:
public class MetaData
{
[NotNull]
public Guid Id { get; set; }
[NotNull]
public Guid RecordId { get; set; }
[NotNull]
public virtual string Description { get; set; }
//Enum that defines the extra properties
public virtual MetaDataTypes MetaDataType { get; set; }
public Dictionary<string,object> ExtraProperties { get; set; }
}
I am using the ExtraProperties field in each object to store a JSON encoded string of additional data for each type, determined by the Enum values noted in the objects above.
What I am trying to find out, is how do I consistently (and efficiently) define those metadata fields on an incoming JSON without needing to hard code each individual type?
For example, I was thinking the JSON for a complete incoming record could be something like this:
{
"Record" : {
"InvoiceNum":"12345",
"Notes":"Notes go here",
"MetaData" : [
{
"MetaDataType" : "Address",
"Properties" :
{
"Address01":"123 ABC Ave",
"City":"New York City",
"Country":"US"
}
},
{
"MetaDataType":"ClientContact",
"Properties" :
{
"FirstName":"John",
"LastName":"Doe",
"Email":"jdoe#example.com",
"Phone":"8675309"
}
}]
}
}
But I want the "Properties" objects to have consistent values based on what the MetaDataType object is defined as and I only want to store the properties for that particular object type in the database attached to that object (there are, across all metadata types, about a hundred unique values).
Hopefully that makes sense, like I said earlier my brain is fried a little today so maybe I am missing something really obvious, but I would like to get your thoughts.
I'm deserializing some JSON from a server which is, for the most part, simple:
{
"id": "ABC123"
"number" 1234,
"configured_perspective": "ComplexPerspective[WithOptions,Encoded]"
}
That "configured_perspective" property, however, is an unfortunate case of the server using a weirdly put-together string when a nested object would have been better.
To ease the suffering of our .NET users, I convert this into a custom class in my object model:
public class Example
{
public string id { get; set; }
public int number { get; set; }
public Perspective configured_perspective { get; set; }
}
// Note, instances of this class are immutable
public class Perspective
{
public CoreEnum base_perspective { get; }
public IEnumerable<OptionEnum> options { get; }
public Perspective(CoreEnum baseArg, IEnumerable<OptionEnum> options) { ... }
public Perspective(string stringRepresentation) {
//Parses that gross string to this nice class
}
public static implicit operator Perspective(string fromString) =>
new Perspective(fromString);
public override string ToString() =>
base_perspective + '[' + String.Join(",", options) + ']';
}
As you can see, I've put together a custom class Perspective that converts to and from the JSON string, but I can't seem to get Newtonsoft JSON to automatically convert the string to my Perspective class.
I tried getting it to call the string constructor with the [JsonConstructor] attribute, but it just calls the constructor with null, not with the string value present in the JSON.
I was under the impression (based on https://stackoverflow.com/a/34186322/529618) that JSON.NET would use implicit/explicit string conversion operators to convert a simple string in JSON to an instance of the target type when available, but it seems to ignore it, and just returns the error:
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type Perspective. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'configured_perspective'
I'm trying to avoid resorting to writing a custom JsonConverter for my Example class - I was pretty sure there would be an out-of-the-box way to convert simple string values to a non-string property type, I just haven't found it yet.
I actually wrote out a custom serializer class before doing reading the last of your article, but I then had an idea.
What if we modified example to not serialize it to Perspective? And we were somewhat lazy about it?
public class Example
{
public string id { get; set; }
public int number { get; set; }
public string configured_perspective { get; set; }
private Perspective _configuredPespective;
[JsonIgnore]
public Perspective ConfiguredPerspective => _configuredPerspective == null ? new Perspective(configured_persective) : _configuredPerspective;
}
It's not perfect, and we hold onto the string wasting memory, but it might work for you as a work-around.
Currently I'm using the following variation on #Jlalonde's suggestion - tweaked such that the user experience doesn't change, taking advantage of the fact that JSON.NET looks for private properties as well.
public class Example
{
public string id { get; set; }
public int number { get; set; }
[JsonIgnore]
public Perspective configured_perspective { get; set; }
[DataMember(Name = "configured_perspective")]
private string configured_perspective_serialized
{
get => configured_perspective?.ToString();
set => configured_perspective = value == null ? null : new Perspective(value);
}
}
i tried to Deserialize this string :
string _jsonObject = {\"Ad\":{\"Type\":\"Request"\,
\"IdAd\":\"xxx#xxx.com\",
\"Category\":\"cat\",
\"SubCategory\":\"subcat\"},
\"Position\":{\"Latitude\":\"38.255\",
\"Longitude\":\"1.2\",
\"Imei\":\"0123456789\"};
}";
Message _message = JsonConvert.DeserializeObject<Message>(_jsonObject);
Works pretty for "Ad" but not instanciate "Position".
Any idea ?
I forgot to make the properties public. Don't forget to do that...
In the interest of helping others that may be experiencing this issue, or one related to it...
In my case, I had an object with an array of other objects, and one of the reference-type properties on those sub-objects was always null after deserialization. I tried all kinds of things, including downloading the JSON.Net source and stepping through it to find the failure point.
To make a long story short, the problem was, of course, my own. Here is a highly simplified version of my JSON and classes.
JSON
{
"$id": "1",
"RowCount": 10,
"Rows": [{
"$id": 2",
"ItemId": "1",
"ItemName": "Some Item",
"Owner": {
"Name": "John Doe",
"Id": "711D04F5-586F-4FD4-8369-4C00B51DD86F",
// other properties...
},
"OwnerId": "711D04F5-586F-4FD4-8369-4C00B51DD86F"
},
// more rows
]
}
Classes
public class Items
{
public int RowCount { get; set; }
public IEnumerable<Item> Rows { get; set; }
}
public class Item
{
private string ownerId;
public string ItemId { get; set; }
public string ItemName { get; set; }
public Person Owner { get; set; }
public string OwnerId
{
get { return this.ownerId; }
set {
if (value != this.ownerId)
{
this.Owner = null;
}
this.ownerId = value;
}
}
}
public class Person
{
public string Name { get; set; }
public string Id { get; set; }
// other properties
}
What was happening is that, because the Owner property appeared in the JSON prior to the OwnerId property, when the OwnerId property was set, the setter code determined that the current value was not the same as the value being set (since the current value was null), so it set the Owner property to null.
To fix it I also check the value being set against the id of the Owner object as well, and skip setting Owner to null if they are the same.
Admittedly, the cause of my problem may not be the same for everyone, but this is at least a cautionary tale to double-check what is happening when your objects are being initialized during deserialization.
I don't know how you are trying to deserialize, but this should work....
string json = "{\"Ad\":{\"Type\":\"Request\", \"IdAd\":\"xxx#xxx.com\", \"Category\":\"cat\", \"SubCategory\":\"subcat\"},\"Position\":{\"Latitude\":\"38.255\", \"Longitude\":\"1.2\", \"Imei\":\"0123456789\"}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
public class Ad
{
public string Type { get; set; }
public string IdAd { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
}
public class Position
{
public string Latitude { get; set; }
public string Longitude { get; set; }
public string Imei { get; set; }
}
public class RootObject
{
public Ad Ad { get; set; }
public Position Position { get; set; }
}
In my case, my class properties had internal setters and after setting them public the problem solved.
In my case there is a more subtle error. It is easy to add leading or trailing spaces in the json keys by mistake. When that happens, the key is not recognized and attempting to deserialize it sets the value to null.
For example: {" id": 123}
This id field is not recognized because of the leading space " id". To fix it, fix the json to have instead "id".
Make sure the name of array in JSON matches with property name in your class
Illustrating (Look for "Components"):
JSON:
{
"Components": [
{
"Attribute1": "ABC",
"Attribute2": "XYZ"
}
]
}
Class:
public class MyClass
{
public IList<Component> Components { get; set; }
}
Deserialize:
JsonConvert.DeserializeObject<MyClass>(File.ReadAllText(#"ComponentSet.json"))
My problem was that I was including the class name at the beginning of my JSON string. I had copy-pasted from the serialized output of another class that contained the one I wanted to deserialize and I had purposefully included the class name thinking this was the correct JSON string. Once I removed the class name from my JSON string, it deserialized just fine.
This article was helpful in realizing this: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/4d766a28-ff38-477f-8abf-48ed01f74cd2/jsonconvertdeserializeobjectlttgtjsonstring-returning-all-propertieslttgt-as-null?forum=wpdevelop
I did not see this answer here so I am including it hoping that it helps those who made the same silly mistake as me.
I've never had any issues using Newtonsoft.Json, but decided to go with built in json libraries in latest project. Ended up with null result. Turns out the following will fail:
JSON:
{
"myProperty": "abc"
}
CLASS:
public void MyClass
{
public string MyProperty { get; set; }
}
Why does it fail? "myProperty" in json is camel case (starts with lower case letter), while MyProperty in MyClass starts with upper case letter. If you make both cases the same it works. I tried figuring out how to configure case insensitivity for the entire app, but apparently that's not possible to do, so I went back to Newtonsoft.JSON and the problem went away.
In my case, it was because I did not have a public constructor on my class.
This is what my class originally looked like:
public class TreeGroup
{
public string Name { get; set; }
public SiteGroup Group { get; set; }
public List<TreeMimicObject> Children { get; set; }
public TreeGroup(SiteGroup item)
{
// Notice this constructor takes a SiteGroup object and there
// is no default constructor
}
}
so I changed the class from the above to this:
public class TreeGroup
{
public string Name { get; set; }
public SiteGroup Group { get; set; }
public List<TreeMimicObject> Children { get; set; }
public TreeGroup()
{
// Added this default constructor here!!
}
public TreeGroup(SiteGroup item)
{
// ...
}
}
and it worked!
In my case the problem was deserializeobject return null when try to convert null value from json to int.
public class ItemCalcResModel
{
public int cartId;
}
I solved the problem by enable nullable in project:
#nullable enable
public class ItemCalcResModel
{
public int? cartId;
}
Background:
I have a custom class, which represents a Data Base Table, each property corresponding to a table column. The properties can be classified in three ways.
Example: Take for example a Person object.
MetaProperties: (Columns that are needed by the program)
Person_ID: used in table for indexing etc...
UserDefinedType: (UDT), complex class handling write-permission on the table.
Timestamp: needed to handle the UDT in C# DataTables
RealProperties: (actual traits that describe the real Person)
FullName
DateOfBirth
PlaceOfBirth
EyeColor
etc... (many more)
RawDataProperties: (these columns hold raw data from external sources)
Phys_EyeColor: the eye-color, as directly imported from the physical traits database, may be in unknown format, may have conflicting value with entry from other db, or any other data quality issue...
HR_FullName: full name as given in HR file
Web_FullName: full name as taken from a web form
Web_EyeColor: eye color as taken from web form
etc...
public class Person
{
#region MetaProperties
public int Person_ID { get; set; }
public UserDefinedType UDT { get; set; }
public DateTime timestamp { get; set; }
#endregion
#region RealProperties
public string FullName { get; set; }
public DateTime DateOfBirth { get; set; }
public string PlaceOfBirth { get; set; }
public Color EyeColor { get; set; }
//...
#endregion
#region RawDataProperties
public string Phys_EyeColor { get; set; }
public string Phys_BodyHeight { get; set; }
public string Web_FullName { get; set; }
public string Web_EyeColor { get; set; }
public string HR_FullName { get; set; }
//...
#endregion
}
Question: How can I programmatically differentiate between these three types of properties in my Person class? The goal is to be able to iterate through properties of a certain type using System.Reflection or some other organisational construct. Pseudocode:
foreach(Property prop in Person.GetPropertiesOfType("RealProperty"){
... doSmth(prop);
}
I'm thinking about writing custom Attributes, and hanging them on to the properties, sort of like taggin.
But since I know nothing about custom Attributes, I would like to ask if I'm going down the proper path, or if there are any other better ways of doing this.
Note: the shown example may may not be the best in terms of program design, and I am well aware that inheritance or splitting up the class otherwise could solve this problem. But that is not my question - I want to know if properties in a class can be tagged or somehow differentiated between using custom categories.
You can do this with custom attributes.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class PropertyAttribute : System.Attribute
{
public PropertyType Type { get; private set; }
public PropertyAttribute (PropertyType type) { Type = type; }
}
public enum PropertyType
{
Meta,
Real,
Raw,
}
Then, you can do this with each property or field:
[PropertyType(PropertyType.Meta)]
public int Person_ID;
[PropertyType(PropertyType.Real)]
public string FullName;
[PropertyType(PropertyType.Raw)]
public string Phys_EyeColor;
Then you can access it with something like
foreach (PropertyAttribute attr in this.GetType().GetCustomAttributes(typeof(PropertyAttribute), false))
{
// Do something based on attr.Type
}
I'm trying to map JSON that looks like
"ids": {
"id": {
"#value":"6763754764235874140"
}
}
And I'd like to map it onto a couple of classes that look like
class Property
{
public Ids Ids { get; set; }
}
class Ids
{
public string Id { get; set; }
}
So basically I want to stuff the value of ids/id/#value from the JSON document into Ids.Id in the class architecture. From browsing the documentation, I thought I could use something like
[JsonProperty(ItemConverterType=typeof(IdConverter))]
public string Id { get; set; }
and provide a custom JsonConverter subclass named IdConverter. When I do, though, my IdConverter.ReadJson never gets called. What am I doing wrong?
Looks like the answer was that ItemConverterType is for converting items in an array. Double-annotating the property with JsonProperty and JsonConverter attributes works:
[JsonProperty(PropertyName="id")]
[JsonConverter(typeof(IdConverter))]
public string Id { get; set; }