manuc66 / jsonsubtypes Goto Github PK
View Code? Open in Web Editor NEWDiscriminated Json Subtypes Converter implementation for .NET
Home Page: https://manuc66.github.io/JsonSubTypes/
License: MIT License
Discriminated Json Subtypes Converter implementation for .NET
Home Page: https://manuc66.github.io/JsonSubTypes/
License: MIT License
Hello i was wondering if you can manage to to deserialize a json that has its Discriminator
outside the Discriminated object:
The below example works
public class Parent{
Child childField{get;set;}
}
[JsonConverter(typeof(JsonSubTypes),CHILDTYPE)]
[JsonSubTypes.KnownSubTypes(typeof(Child1),CTYPE.Child1)]
[JsonSubTypes.KnownSubTypes(typeof(Child2),CTYPE.Child2)]
public abstract class Child{
public enum Discriminator{
Child1=0,
Child2=1
}
private const string CHILDTYPE="childType"
[JsonProperty(CHILDTYPE)]
public abstract Discriminator Kind{get;}
}
public class Child1:Child{
public int Value{get;set;}
public override Kind=>Discriminator.Child1;
}
public class Child2:Child{
public bool Value{get;set;}
public override Kind=>Discriminator.Child2;
}
What i want to achieve :
Could you move the Kind
field to the parent object and decorate the parent so that it uses the Kind
field to discriminate the childField
?
Detailed question here
Long story short
Can i accomodate from this json:
{
"childField":{ "Kind":3}
}
To this json:
{
"Kind":3,
"childField":{}
}
I have the following classes:
public class Quote
{
[JsonProperty("id"), JsonRequired]
public int Id { get; set; }
[JsonProperty("type"), JsonRequired]
public string Type { get; set; }
[JsonProperty("status"), JsonRequired]
public long Status { get; set; }
[JsonProperty("insurer"), JsonRequired]
public Insurer Insurer { get; set; }
[JsonProperty("price")]
public string Price { get; set; }
[JsonProperty("coverage"), JsonRequired]
public CoverageBase Coverage { get; set; }
}
// Bonus points if I can use this instead of `string Type` in `Quote`.
public enum CoverageType
{
Auto,
Renter,
}
// If this needs to be an empty interface I can do that.
public abstract class CoverageBase { }
public class AutoCoverage : CoverageBase
{
[JsonProperty("bodily_injury_liability_per_person"), JsonRequired]
public int BodilyInjuryLiabilityPerPerson { get; set; }
[JsonProperty("bodily_injury_liability_per_accident"), JsonRequired]
public int BodilyInjuryLiabilityPerAccident { get; set; }
[JsonProperty("uninsured_motorist_per_person"), JsonRequired]
public int UninsuredMotoristPerPerson { get; set; }
[JsonProperty("uninsured_motorist_per_accident"), JsonRequired]
public int UninsuredMotoristPerAccident { get; set; }
[JsonProperty("property_damage_liability"), JsonRequired]
public int PropertyDamageLiability { get; set; }
[JsonProperty("collision_deductible")]
public int? CollisionDeductible { get; set; }
[JsonProperty("comprehensive_deductible")]
public int? ComprehensiveDeductible { get; set; }
[JsonProperty("comprehensive_type"), JsonRequired]
public string ComprehensiveType { get; set; }
[JsonProperty("medical_payment"), JsonRequired]
public int MedicalPayment { get; set; }
[JsonProperty("towing_road_service"), JsonRequired]
public bool TowingRoadService { get; set; }
[JsonProperty("rental_reimbursement"), JsonRequired]
public bool RentalReimbursement { get; set; }
[JsonProperty("ride_sharing"), JsonRequired]
public bool RideSharing { get; set; }
}
public class RenterCoverage : CoverageBase
{
[JsonProperty("coverage_a_dwelling"), JsonRequired]
public int CoverageADwelling { get; set; }
[JsonProperty("coverage_b_other_structures"), JsonRequired]
public int CoverageBOtherStructures { get; set; }
[JsonProperty("coverage_c_personal_property"), JsonRequired]
public int CoverageCPersonalProperty { get; set; }
[JsonProperty("coverage_loss_of_use_type"), JsonRequired]
public string CoverageLossOfUseType { get; set; }
[JsonProperty("coverage_d_loss_of_use"), JsonRequired]
public int CoverageDLossOfUse { get; set; }
[JsonProperty("coverage_e_liability"), JsonRequired]
public int CoverageELiability { get; set; }
[JsonProperty("coverage_f_medical_per_person"), JsonRequired]
public int CoverageFMedicalPerPerson { get; set; }
[JsonProperty("coverage_f_medical_per_accident"), JsonRequired]
public int CoverageFMedicalPerAccident { get; set; }
[JsonProperty("deductible"), JsonRequired]
public int Deductible { get; set; }
[JsonProperty("extended_replacement_cost_dwelling"), JsonRequired]
public int ExtendedReplacementCostDwelling { get; set; }
}
This is the type == "Auto"
JSON:
{
"id": 123,
"type": "Auto",
"status": 17,
"insurer": {
"id": 12,
"name": "MyInsABC",
"logo": "https://foo.com/logo.png"
},
"price": "1234.56",
"coverage": {
"bodily_injury_liability_per_person": "50000",
"bodily_injury_liability_per_accident": "100000",
"uninsured_motorist_per_person": "50000",
"uninsured_motorist_per_accident": "100000",
"property_damage_liability": "50000",
"collision_deductible": "1000",
"comprehensive_deductible": "1000",
"comprehensive_type": "ACV",
"medical_payment": "5000",
"towing_road_service": true,
"rental_reimbursement": false,
"ride_sharing": true
}
}
And type == "Renters"
:
{
"id": 1235,
"type": "Renters",
"status": 19,
"insurer": {
"id": 27,
"name": "MyInsABC",
"logo": "https://foo.com/logo.png"
},
"price": "130.00",
"coverage": {
"coverage_c_personal_property": "25000",
"coverage_loss_of_use_type": "LS",
"coverage_d_loss_of_use": "5000",
"coverage_e_liability": "100000",
"coverage_f_medical_per_person": "1000",
"coverage_f_medical_per_accident": "1000",
"deductible": "500"
}
}
As you can see the discriminator is type
on the parent type Quote
which determines how coverage
should be parsed.
Does JsonSubTypes support this?
If so, how do I need to annotate each class?
Thank you very much for your time!
// Put the types you are serializing or deserializing here
[Serializable]
[JsonConverter(typeof(JsonSubtypes),_type)]
[JsonSubTypes.JsonSubtypes.KnownSubType(typeof(EventClass.PlayerAction),EventType.PLAYER_ACTION)]
[JsonSubTypes.JsonSubtypes.KnownSubType(typeof(EventClass.GroupAction), EventType.GROUP_ACTION)]
public abstract class EventClass {
public enum EventType {
PLAYER_ACTION = 0,
GROUP_ACTION = 1
}
private const string _type = "type";
[JsonProperty(_type)]
public abstract EventType Discriminator { get; }
}
public class GroupAction : EventClass {
public override EventType Discriminator => EventType.GROUP_ACTION;
[JsonProperty("eventId")]
public long EventId { get; set; }
[JsonProperty("groupId")]
public long GroupId { get; set; }
}
public class PlayerAction : EventClass {
public override EventType Discriminator =>EventType.PLAYER_ACTION;
[JsonProperty("eventId")]
public long EventId { get; set; }
[JsonProperty("playerId")]
public long PlayerId { get; set; }
}
{
"class":{
"type":0,
"playerId":33,
"eventId":44
},
"data":{
"action":"finished columbus "
}
}
{"message":"Place your serialized or deserialized JSON here"}
I expect the json to be deserialized to a PlayerAction
class.
System.NotSupportedException: Deserialization of reference types without parameterless constructor is not supported. Type 'LS.Models.EventClass'
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_DeserializeMissingParameterlessConstructor(Type invalidType)
at System.Text.Json.JsonSerializer.HandleStartObject(JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadCore(JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadAsync[TValue](Stream utf8Json, Type returnType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<g__Bind|0>d.MoveNext()
// Your calls to here
`I have tried putting a parameterless constructor in the base class to no avail.And i have also tried putting parameterless ctors in the derived classes.I still get the same error.
Hi,
when deserializing expression trees we have the requirement that subtypes themselves can contain the base class / interface.
Assume the following class to be deserialized:
[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(BinaryExpression), "Binary")]
[JsonSubtypes.KnownSubType(typeof(ConstantExpression), "Constant")]
public interface IExpression
{
string Type { get; }
}
public class BinaryExpression : IExpression
{
public string Type { get; } = "Binary";
public IExpression SubExpressionA { get; set; }
public IExpression SubExpressionB { get; set; }
}
public class ConstantExpression : IExpression
{
public string Type { get; } = "Constant";
public string Value { get; set; }
}
Currently the deserialization fails:
[Fact]
public void TestIfNestedObjectIsDeserialized()
{
var binary = JsonConvert.DeserializeObject<IExpression>("{\"Type\":\"Binary\"," +
"\"SubExpressionA\":{\"Type\":\"Constant\",\"Value\":\"A\"}," +
"\"SubExpressionB\":{\"Type\":\"Constant\",\"Value\":\"B\"}" +
"}");
Assert.Equal(typeof(ConstantExpression), (binary as BinaryExpression)?.SubExpressionA.GetType());
}
The reason seems to be that the CanRead is set to false as soon as the subtype type is known. So my current workaround is something like that:
protected object _ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, obj);
return obj;
}
This works and all the tests are green, but it feels like a hack and some Json.Net features are likely to be broken (e.g. ObjectCreation behaviour).
Do you have a better idea to implement this feature?
It would be great if the NuGet package was strong named.
https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/strong-naming#create-strong-named-net-libraries
Importing the package from NuGet (v1.5.2.0), I'm getting the warning "Referenced assembly 'JsonSubTypes, Version=15.2.0, Culture=neutral, PublicKeyToken=null' does not have a strong name"
Install JsonSubTypes via NuGet on any strongly named assembly.
I was wondering why this project targets so many versions of netstandard?
netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;
why not just target netstandard1.3?
public abstract class BaseType{
protected dynamic internalvalue;
}
public class IscBool: BaseType {
public bool Value {
get { return (bool)internalvalue; }
set { internalvalue = (bool)value; }
}
public IscBool(bool val) {
Value = val;
}
}
public class IscNumber : BaseType{
public double Value {
get { return (double)internalvalue; }
set { internalvalue = (double)value; }
}
public IscNumber(double val) {
Value = val;
}
}
public class IscString: BaseType {
public string Value {
get { return (string)internalvalue; }
set { internalvalue = (string)value; }
}
public IscString(string val) {
Value = val;
}
}
{"Value":12.0}
I expected "$type":2 to be added in the serialized JSON
The discriminator is not added at all
var jsonsettings = new JsonSerializerSettings();
jsonsettings.Converters.Add(JsonSubtypesConverterBuilder
.Of(typeof(BaseType), "$type")
.RegisterSubtype(typeof(IscString), TypeDiscriminator.IscString)
.RegisterSubtype(typeof(IscNumber), TypeDiscriminator.IscNumber)
.RegisterSubtype(typeof(IscBool), TypeDiscriminator.IscBool)
.Build());
IscNumber testnumber = new IscNumber(12);
var jsontest = JsonConvert.SerializeObject(testnumber, jsonsettings);
i have the classes from below
[DataContract]
[JsonConverter(typeof(JsonSubtypes), "shapeType")]
[JsonSubtypes.KnownSubType(typeof(Shape2DReactangle), "Shape2DReactangle")]
[JsonSubtypes.KnownSubType(typeof(Shape2DPolygon), "Shape2DPolygon")]
public partial class Shape : IEquatable<Shape>, IValidatableObject
{...
[DataContract]
public partial class Shape2DPolygon : Shape, IEquatable<Shape2DPolygon>, IValidatableObject
{...
[DataContract]
public partial class Shape2DReactangle : Shape, IEquatable<Shape2DReactangle>, IValidatableObject
{
and they are generated from this openapi json
"Shape": {
"type": "object",
"discriminator": {
"propertyName": "shapeType",
"mapping": {
"2d-rectangle": "#/definitions/Shape2DReactangle",
"2d-polygon": "#/definitions/Shape2DPolygon"
}
},
"required": [
"shapeId"
],
"properties": {
"shapeId": {
"type": "string
}
}
},
when i try to serialize it with
var stg = new JsonSerializerSettings();
stg.Converters.Add(JsonSubTypes.JsonSubtypesConverterBuilder
.Of(typeof(Org.OpenAPITools.Model.Shape), "shapeType")
.RegisterSubtype(typeof(Shape2DReactangle), "Shape2DReactangle")
.RegisterSubtype(typeof(Shape2DPolygon), "Shape2DPolygon")
.SerializeDiscriminatorProperty() // ask to serialize the type property
.Build());
var serial = JsonConvert.SerializeObject(position, stg);
i don't get the shapeType field in json
The Property that defines the type switching seems to be directly tied to the Class Name. This may be by design but seems to me it should be independent. I would think that once you find the type that belongs to the Kind property it should create that type and not use the value of the Kind property to create the type.
Could not create an instance of type IAnimal. Type is an interface or abstract class and cannot be instantiated. Path 'Kind', line 1, position 8.
[JsonConverter(typeof(JsonSubtypes), "Kind")]
public interface IAnimal
{
string Kind { get; }
}
public class Dog : IAnimal
{
public string Kind { get; } = "DogX";
public string Breed { get; set; }
}
var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Kind\":\"DogX\",\"Breed\":\"Jack Russell Terrier\"}");
Hi there,
I wanted to use your package but I'm having issues with my use case.
Here's the JSON:
{
"id": 0,
"in": [
{
"disc": "a"
},
{
"prop": "propr"
}
]
}
What I'd like to do is to be able to discriminate based on the disc
value, which is in the first child of in
property.
My idea was to have something like that in my base class:
[JsonConverter(typeof(JsonSubtypes), nameof(In))]
[JsonSubtypes.KnownSubType(typeof(ChildRequest), "a")]
[JsonSubtypes.KnownSubType(typeof(ChildRequest2), "b")]
public abstract class BaseClass {
[JsonProperty("in")]
public In[] In{ get; set; }
public string Disc => In.First().Disc;
}
but it's not working, as Json.Net tries to deserialize it into BaseClass
(which can't be initialized as it's abstract
).
Is there a way to do something like that or am I out of luck?
Given:
[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public interface IAnnimal
{
string Sound { get; }
}
public class Dog : IAnnimal
{
public string Sound { get; } = "Bark";
public string Breed { get; set; }
}
public class Cat : IAnnimal
{
public string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}
On single thread call this test succeed:
var annimal = JsonConvert.DeserializeObject<IAnnimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.Equal("Jack Russell Terrier", (annimal as Dog)?.Breed);
But once run deserialisation is parallelized it fails:
Action test = () =>
{
var annimal = JsonConvert.DeserializeObject<IAnnimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
Assert.Equal("Jack Russell Terrier", (annimal as Dog)?.Breed); ;
};
Parallel.For(0, 100, index => test());
Hi,
I tested the new and very cool "custom enum" - feature and noticed that the assembly published on Nuget seems to be an older version of JsonSubTypes. When I build it from source it works fine.
Make the library support net40 framework
Hello, awesome package you have here. However, I have found a potential bug when you are serializing a base type while enabling discriminators. I have added a failing test below.
public class Test
{
[Fact]
public void BreakingTest()
{
var input = new[] {new Cat(), new Animal()};
var serializersettings = new JsonSerializerSettings();
serializersettings.Converters.Add(
JsonSubtypesConverterBuilder
.Of(typeof(Animal), "Type")
.SerializeDiscriminatorProperty()
.RegisterSubtype(typeof(Cat), typeof(Cat).Name)
.Build());
var result = String.Empty;
var exception = Record.Exception(() => result = JsonConvert.SerializeObject(input, serializersettings));
Assert.Null(exception);
Assert.Equal("[{\"Type\":\"Cat\"},{\"Type\":\"Animal\"}]", result);
}
}
public class Cat : Animal{}
public class Animal{}
When you serialize a base type with discriminators enabled, it'll be serialized with the base type as the discriminator value. See also provided test case.
I get the following error:
System.Collections.Generic.KeyNotFoundException: The given key '(...).Animal' was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at JsonSubTypes.JsonSubtypesConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(JsonWriter writer, JsonConverter converter, Object value, JsonContract contract, JsonContainerContract collectionContract, JsonProperty containerProperty) in /_/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs:line 652
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) in /_/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs:line 691
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) in /_/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs:line 80
(...)
It would be cool if ConvertJsonValueToType() could also parse enum values which are stored in JSON as string and not only int enum representation... Eg.:
JsonConvert.DeserializeObject<MainClass>("{\"SubTypeData\":{\"ZzzField\":\"zzz\",\"SubTypeType\":\"WithAaaField\"}}")
Event better if there would be implemented System.Runtime.Serialization.EnumMember value handling. Eg. for enum:
public enum SubType
{
[System.Runtime.Serialization.EnumMember(Value = "aaaField")]
WithAaaField,
WithZzzField
}
then in JSON:
JsonConvert.DeserializeObject<MainClass>("{\"SubTypeData\":{\"ZzzField\":\"zzz\",\"SubTypeType\":\"aaaField\"}}")
Do you think that it could be implemented?
By the way - JsonSubTypes is great! Thanks.
Here's my current setup:
[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(A), PriceDiscountType.A)]
[JsonSubtypes.KnownSubType(typeof(B), PriceDiscountType.B)]
[JsonSubtypes.KnownSubType(typeof(C), PriceDiscountType.C)]
[JsonSubtypes.KnownSubType(typeof(D), PriceDiscountType.D)]
[JsonSubtypes.KnownSubType(typeof(E), PriceDiscountType.E)]
[JsonSubtypes.KnownSubType(typeof(F), PriceDiscountType.F)]
[JsonSubtypes.KnownSubType(typeof(G), PriceDiscountType.G)]
public abstract class PriceDiscount : Core.Toolkit.ICloneable
However, at times the default serialization is to use camel case. Is there a way to make this work for both camel case, and non camel case, so that I don't have to worry about which is being used?
Given:
[JsonConverter(typeof(JsonSubtypes), "ClassName")]
public class Animal
{
public virtual string ClassName { get; }
public string Color { get; set; }
}
public class Faulty
{
public static bool ctorCalled = false;
public Faulty()
{
ctorCalled = true;
}
}
When:
JsonConvert.DeserializeObject<Animal>(@"{""ClassName"": ""Faulty""}")
Then the contructor of the class Faulty
is invoked, but it shouldn't !
See PR #62
Implement the same feature as #26 but for KnownSubTypeWithProperty
attribute.
Could you update the application so it supports the default json formatter in .Net core 3?
Hi @manuc66,
How to serialize into JSON include typeinfo from anotation.
[JsonConverter(typeof(JsonSubtypes), "_type")]
I would like to avoid having an extra "type" property in my sub-types.
Motivation: It is cleaner to avoid the extra property (avoid duplicate information)
[JsonConverter(typeof(JsonSubtypes), "type")]
[JsonSubtypes.KnownSubType(typeof(Dog), AnimalType.Dog)]
[JsonSubtypes.KnownSubType(typeof(Cat), AnimalType.Cat)]
public abstract class Animal
{
public int Age { get; set; }
}
public class Dog : Animal
{
public bool CanBark { get; set; } = true;
}
public class Cat : Animal
{
public int Lives { get; set; } = 7;
}
public enum AnimalType
{
Dog = 1,
Cat = 2
}
Deserializing is working perfectly:
[{"canBark":false,"type":1,"age":3},{"lives":6,"type":2,"age":11}]
JsonConvert.DeserializeObject<Animal[]>(json, settings);
// outputs correct array of [Dog, Cat]
Serialization not so much:
JsonConvert.SerializeObject(new Dog {Age = 3, CanBark = false}, settings);
{"canBark":false,"age":3}
It seems like it would be necessary to annotate all sub-types with [JsonConverter]
, and it would require reflection to get the parent type that contains the type mapping.
This is NOT a bug with JsonSubTypes. However I think it's worth while to point this out for others attempting to use it.
You'll end up hitting this bug: dotnet/standard#481
Is your feature request related to a problem? Please describe.
Can you provide mechanism for WriteJson method ?
This library works very well for deserialize but serialization it's not implemented and i need it to store polymorphism in EventStore or MongoDb for a CQRS system
Describe the solution you'd like
Use WriteJson method to produce same json as ReadJson
Additional context
Polymorphism for fields abstraction in a CMS over CQRS/ES (event store don"t understand inheritance of objects by default)
*** Code
[JsonConverter(typeof(JsonSubtypes), "kind")]
[JsonSubtypes.KnownSubType(typeof(StringFieldEntity), "string")]
public abstract class FieldEntity : Entity<FieldId>
{
public string Name { get; set; }
protected FieldEntity(FieldId id, string name) : base(id)
{
Name = name;
}
}
*** Source JSON
{"kind":"string", "value":"test"}
*** Destination JSON
{"kind":"string", "value":"test"}
Is your feature request related to a problem? Please describe.
I am trying to deserialize an array of items which derive from a base class. These items don't have a $type
property but do have a similar property by a different name, which is set to a constant string per property type. I am able to use KnownSubTypeWithProperty
to get pretty close but I don't think this solution will continue to work as more subtypes are added.
Describe the solution you'd like
An attribute that allows you to specify the type, property name, and property value to look for.
[JsonSubtypes.KnownSubTypeWithPropertyValue(typeof(HeaderWidget), "widget_type", "HEADER")]
[JsonSubtypes.KnownSubTypeWithPropertyValue(typeof(EmailWidget), "widget_type", "EMAIL")]
Describe alternatives you've considered
I am currently using KnownSubTypeWithProperty
to differentiate between the types but two of my types are identical except for the widget_type
field.
EmailWidget
{
"label": {
"text": "Email",
"hint": "Original text: Email"
},
"required": true,
"order": 4,
"widget_type": "EMAIL"
}
TextWidget
{
"label": {
"text": "Comments",
"hint": "Original text: Comments"
},
"order": 3,
"widget_type": "TEXT"
}
Right now I am differentiating by not having any of the TextWidget
s that I have specified include the required
property, but that's not going to work in all cases. I could also make these two types actually be the same class but I think that is a less than ideal solution since all other types have their own class and I'd like to keep it consistent.
Additional context
I think this is related to #69 but not quite the same.
*** Source/destination types
This is a subset of the types I have.
[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(HeaderWidget), "text")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(NameWidget), "placeholder_last_name")]
public abstract class Widget
{
[Newtonsoft.Json.JsonProperty("order", Required = Newtonsoft.Json.Required.Always)]
public int Order { get; set; }
[Newtonsoft.Json.JsonProperty("widget_type", Required = Newtonsoft.Json.Required.Always)]
public string WidgetType { get; set; }
}
public class HeaderWidget : Widget
{
public const string WIDGET_TYPE = "HEADER";
public HeaderWidget()
{
WidgetType = WIDGET_TYPE;
}
[Newtonsoft.Json.JsonProperty("text", Required = Newtonsoft.Json.Required.Always)]
public string Text { get; set; }
}
[ExcludeFromCodeCoverage]
public class NameWidget : Widget
{
public const string WIDGET_TYPE = "NAME";
public NameWidget()
{
WidgetType = WIDGET_TYPE;
}
[Newtonsoft.Json.JsonProperty("label", Required = Newtonsoft.Json.Required.Always)]
public WidgetLabel Label { get; set; }
[Newtonsoft.Json.JsonProperty("placeholder_last_name", Required = Newtonsoft.Json.Required.Always)]
public string PlaceholderLastName { get; set; }
[Newtonsoft.Json.JsonProperty("placeholder_first_name", Required = Newtonsoft.Json.Required.Always)]
public string PlaceholderFirstName { get; set; }
}
*** Source/destination JSON
{
"text": "Gift amount",
"order": 0,
"widget_type": "HEADER"
},
{
"label": {
"text": "Name",
"hint": "Original text: Name"
},
"placeholder_last_name": "Last name",
"placeholder_first_name": "First name",
"order": 3,
"widget_type": "NAME"
}
I have the following JSON:
{
"offers": [
{
"offerType": "Subscription",
"pricingModel": {
"period": "month",
"duration": 3
}
},
{
"offerType": "Custom",
"pricingModel": {
"value": "",
"caption": ""
}
},
{
"offerType": "Unit",
"pricingModel": null
}
]
}
As you can probably determine I want to deserialize pricingModel
into different types depending on the value of offerType
.
Assume I have a base type:
public abstract class BasePricingModel {} // can drop `abstract` (or be an interface) if necessary
And child types:
public class CustomPricingModel : BasePricingModel
{
public string Value { get; set; }
public string Caption { get; set; }
}
public class SubscriptionPricingModel : BasePricingModel
{
public string Period { get; set; }
public int Duration { get; set; }
}
How should I configure this package / newtonsoft.json / asp.net core 2.1 to achieve what I want here?
Thank you very much for your time!
It doesn't appear that newtonsoft 10.0.3 is required currently. Any chance we could have JsonSubTypes change that dependency to 9.0.1?
In an effort to help, I made the change:
#40
The tests all passed on my local machine, however it looks like the CICD failed.
The issue occurs when naming strategy other than CamelCase is used.
public enum EnumType
{
EnumMemberOne,
EnumMemberTwo
}
public interface IMyType
{
EnumType EnumValue {get;}
}
public class MyTypeOne : IMyType
{
public EnumType EnumValue => EnumType.EnumMemberOne;
}
public class MyTypeTwo : IMyType
{
public EnumType EnumValue => EnumType.EnumMemberTwo;
}
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy(),
},
Converters = new List<JsonConverter>
{
new StringEnumConverter
{
NamingStrategy = new SnakeCaseNamingStrategy()
},
JsonSubtypesConverterBuilder
.Of(typeof(IMyType), nameof(IMyType.EnumValue))
.RegisterSubtype(typeof(MyTypeOne), EnumType.EnumMemberOne)
.RegisterSubtype(typeof(MyTypeTwo), EnumType.EnumMemberTwo)
.Build()
}
}
{"enum_value":"enum_member_one"}
JSON is deserialized correctly with regard to naming strategy settings.
Deserialization fails with the following message:
System.ArgumentException: Could not convert 'enum_member_one' to EnumType. ---> Newtonsoft.Json.JsonSerializationException: Error converting value "enum_member_one" to type 'Namespace.EnumType'. Path 'enum_value', line 1, position 13. ---> System.ArgumentException: Requested value 'enum_member_one' was not found.
var json = "{\"enum_value\":\"enum_member_one\"}";
var result = JsonConvert.DeserializeObject<IMyType>(jsonValue, serializerSettings);
I am trying to implement a similar feature.
[JsonConverter(typeof(JsonSubtypes), "searchType")]
[JsonSubtypes.KnownSubType(typeof(CountrySearchDto), "country")]
[JsonSubtypes.KnownSubType(typeof(StateSearchDto), "state")]
public class SearchDto
{
public string SearchType { get; set; }
}
public class CountrySearchDto : SearchDto
{
}
public class StateSearchDto : SearchDto
{
}
[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "SearchType")]
[JsonInheritanceAttribute("CountrySearchDto", typeof(CountrySearchDto))]
[JsonInheritanceAttribute("SearchSearchDto", typeof(StateSearchDto))]
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.13.18.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class SearchDto
{
[Newtonsoft.Json.JsonProperty("searchType", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string SearchType { get; set; }
public string ToJson()
{
return Newtonsoft.Json.JsonConvert.SerializeObject(this);
}
public static SearchDto FromJson(string data)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<SearchDto>(data);
}
}
When I execute the generated code, it serializes the object to something like this:
{
"SearchType":"CountrySearchDto",
"searchType":"Country"
}
It always inserts the discriminator. This json is successfully sent to the webapi but when the client receives the response, it attempts to deserialize using the discriminator "SearchType" which is not part of the response. However, "searchType is part of the response. As a result, this line of code
var discriminator = Newtonsoft.Json.Linq.Extensions.Value<string>(jObject.GetValue(_discriminator));
fails.
Any help would be greatly appreciated.
Is there a way to fallback to a default type when the discriminator value is not present in the registered sub types?
void Main()
{
var o = JsonConvert.DeserializeObject<Base>(@"{""type"": ""UnKnown1""}");
o.Dump();
}
// Define other methods and classes here
enum Types
{
Known1,
Known2,
UnKnown1,
UnKnown2,
UnKnown3
}
[JsonConverter(typeof(JsonSubtypes), "type")]
[JsonSubtypes.KnownSubType(typeof(Known1), Types.Known1)]
[JsonSubtypes.KnownSubType(typeof(Known2), Types.Known2)]
[JsonSubtypes.KnownSubType(typeof(Unknown), Types.UnKnown1)] // <<- How to avoid this?
[JsonSubtypes.KnownSubType(typeof(Unknown), Types.UnKnown2)] // <<- How to avoid this?
[JsonSubtypes.KnownSubType(typeof(Unknown), Types.UnKnown3)] // <<- How to avoid this?
class Base
{
public Types Type { get; set; }
}
class Unknown : Base
{
}
class Known1 : Base
{
}
class Known2 : Base
{
}
[JsonConverter(typeof(JsonSubtypes), "Kind")]
public interface IAnimal
{
string Kind { get; }
}
public class Dog : IAnimal
{
public string Kind { get; } = "Dog";
public string Breed { get; set; }
}
public class Cat : IAnimal { // SCS0535 'Cat' does not implement interface member 'IAnimal.Kind'
public bool Declawed { get; set;}
}
Make the library support net35 framework
[JsonConverter(typeof(JsonSubtypes), nameof(Kind))]
[JsonSubtypes.KnownSubType(typeof(ImplementationA1), "a1")]
[JsonSubtypes.KnownSubType(typeof(ImplementationA2), "a2")]
public interface IInterfaceA
{
string Kind { get; }
IInterfaceB SubObject { get; set; }
}
public class ImplementationA1 : IInterfaceA
{
public string Kind { get; set; } = "a1";
public IInterfaceB SubObject { get; set; }
}
public class ImplementationA2 : IInterfaceA
{
public string Kind { get; set; } = "a2";
public IInterfaceB SubObject { get; set; }
}
[JsonConverter(typeof(JsonSubtypes), nameof(Kind))]
[JsonSubtypes.KnownSubType(typeof(ImplementationB1), "b1")]
[JsonSubtypes.KnownSubType(typeof(ImplementationB2), "b2")]
public interface IInterfaceB
{
string Kind { get; }
int Value { get; set; }
}
public class ImplementationB1 : IInterfaceB
{
public string Kind { get; set; } = "b1";
public int Value { get; set; }
}
public class ImplementationB2 : IInterfaceB
{
public string Kind { get; set; } = "b2";
public int Value { get; set; }
}
None due to serialization/deserialization round-trip for testing purposes:
private static string serialize<T>(T obj) {
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var jsonTextWriter = new JsonTextWriter(streamWriter))
{
var serializer = new JsonSerializer();
serializer.Serialize(jsonTextWriter, obj);
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
The object obj
should get converted into a JSON document.
The serialization result is always an empty string.
var obj = new ImplementationA1
{
SubObject = new ImplementationB1
{
Value = 10,
},
};
Assert.AreNotEqual("", serialize(obj));
Everything just serializes and deserializes fine, if it is done via the JsonConvert
class. However large parts of our software directly use the JsonSerializer
class to serialize and deserialize objects. As now the need for discriminating serialization types arises and we're trying to leverage this library for the task, we're looking for some hint on why JsonConvert
and JsonSerializer
works differently with this library and for hopefully some hint on how to get them going together. I've already had a look into the Newtonsoft's source code but without debugging it, I wasn't able to find some crucial differences in serialization handling. Any help or clarification is appreciated.
I'm pretty sure there was a typo.
JsonSubTypes/JsonSubTypes/JsonSubtypes.cs
Lines 135 to 136 in e44d17b
Update dependency
[JsonConverter(typeof(JsonSubTypes.JsonSubtypes), "Discriminator")]
abstract class Base {
}
class Derived : Base {
public string Discriminator => "Derived";
}
JsonConvert.DeserializeObject<Base>("{\"Discriminator\":\"Base\"}")
JsonSubTypes should throw a JsonSerializationException
.
JsonSubTypes goes into an infinite loop and hangs.
Enable the serializer to be compatible with the DataContractJsonSerializer built in to .NET if API's were already using that. This expects the discriminator to come first.
@hurco-robertsi
There is code quality issue regarding the number of section of this kind:
-#if (NET35 || NET40)
-#else
-#endif
where each branch contains sometimes code that could be considered duplicate.
Here are the currently supported frameworks:
<TargetFrameworks>netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;net35;net40;net45;net46;net47</TargetFrameworks>
and as netstandard2.0 has been release it would be nice to add it to the list:
<TargetFrameworks>netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;net35;net40;net45;net46;net47</TargetFrameworks>
Feature #45 made it an option to avoid breaking RAW JSON/text consumer but it should ideally be placed the first by default.
This feature would need to be implemented in a future breaking change version
What Licence is this Project?
"LICENSE" file say MIT
"JsonSubtypes.cs" File say "Apache License, Version 2.0"
Love your implementation. I was curious any thoughts to adding support for doing the mapping on the child instance instead of the parent? This would provide better support for a more flexible IoC pattern -
Example: The interface is defined in one assembly by a framework author
public interface IAnimal
{
string Breed {get;set;}
}
and then the concrete implementation would be in another file (or assembly)
[JsonSubType(typeof(IAnimal), PropertyName="Breed", PropertyValue="Dog")]
public class Dog : IAnimal
{
public string Breed {get;set;} = "Dog";
public bool HasChip {get;set;}
}
[JsonSubType(typeof(IAnimal), PropertyName="Breed", PropertyValue="Cat")]
public class Cat : IAnimal
{
public string Breed {get;set;} = "Cat";
public bool HasClaws {get;set;}
}
this could be implemented using the DependencyInjection or by using Reflection for all known types loaded (supporting the DI pattern would be more ideal I think)
Thoughts?
I'd like to suggest an enhancement to support fallback to JSONPath when looking for property name and/or value.
Describe the solution you'd like
An ability to use nested field in json as a discriminator name (ex. data.field) for
[JsonConverter(typeof (JsonSubtypes), "data.field")]
[JsonSubtypes.KnownSubType(typeof (SomeClass), "value")]
{
data: {
field: "value"
}
}
It is useful when creating C# model for given API, which you cannot change.
Fallback to JSONPath requires minimal changes (using JObject.SelectToken(discriminatorName) when it had not been found using existing method).
I have implemented described changes in the library, it passes all unit tests, and I use the change in my project. I'd be glad to do the pull request if you'd accept this enhancement.
Code sample
public class DtoTest
{
[Fact]
public void FooParsing()
{
CheckDeserialization<Foo>("{\"msgType\":\"1\"}");
}
private static void CheckDeserialization<T>(string json) where T : class
{
var result = JsonConvert.DeserializeObject<DtoBase>(json);
Assert.IsType<T>(result);
}
}
[JsonConverter(typeof(JsonSubtypes), nameof(DtoBase.MsgType))]
[JsonSubtypes.KnownSubType(typeof(Foo), 1)]
public abstract class DtoBase
{
public virtual int MsgType { get; }
}
class Foo : DtoBase
{
public override int MsgType { get; } = 1;
}
Test passed
Newtonsoft.Json.JsonSerializationException : Could not create an instance of type Sample.Test.DtoBase. Type is an interface or abstract class and cannot be instantiated. Path 'msgType', line 1, position 11.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at JsonSubTypes.JsonSubtypes.ThreadStaticReadObject(JsonReader reader, JsonSerializer serializer, JToken jToken, Type targetType)
at JsonSubTypes.JsonSubtypes.ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Sample.Test.DtoTest.CheckDeserialization[T](String json) in C:\Users\Alex\Documents\Repo\SampleEthereum\Sample.Test\UnitTest1.cs:line 17
at Sample.Test.DtoTest.FooParsing() in C:\Users\Alex\Documents\Repo\SampleEthereum\Sample.Test\UnitTest1.cs:line 12
Test passes for [JsonConverter(typeof(JsonSubtypes), "msgType")]
attribute value. camelCase is hardcoded somewhere.
For whatever reason this library seems to mess with Asp.Net Core List Model Validation by removing indeces. For example:
Say I have an object called person that has a property children which is an array of person, if there is a validation on a person that they must have a gender. Then if one of the children has an invalid gender like (somerandomvalue) asp.net core validation should show an index in the property name. Like below:
{
"children[0].gender": [
"Error converting value \"somerandomvalue\" to type 'Namespace+Gender'. Path 'children[0].gender', line 6, position 16."
]
}
I was unable to figure out why from the code in your github, but i stripped literally everything else away from my project and then finally removed JsonSubTypes and the index came back. I then went and created a new project and it had the indeces no matter what I did. Maybe you'll be able to figure out why.
If i have a 3-level hierarchy and i place a JsonProperty at first and second level
Animal
Dog:Animal
Husky:Dog
Can i deserialize like:
var dog=new Husky();
var data= JsonConvert.SerializeObject<Husky>();
Animal a=JsonConvert.DeserializeObject<Animal>();
So far i get an error and i can only deserialize it like this:
Animal a=JsonConvert.DeserializeObject<Dog>();
P.S My question is better seen here https://stackoverflow.com/questions/54835920/deserializing-with-multiple-levels-of-polymorphic-type-hierarchy
Sample code:
using System;
using Newtonsoft.Json;
using JsonSubTypes;
namespace Test60
{
class Program
{
static void Main(string[] args)
{
Payload run = new Payload.Game.Run();
var data = JsonConvert.SerializeObject(run);
Console.WriteLine(data);
var run2 = JsonConvert.DeserializeObject<Payload>(data);
Console.WriteLine("Hello World!");
}
}
[JsonConverter(typeof(JsonSubTypes.JsonSubtypes), _payloadKind)]
[JsonSubtypes.KnownSubType(typeof(Payload.Game), Discriminator.GAME)]
[JsonSubtypes.KnownSubType(typeof(Payload.Com), Discriminator.COM)]
public abstract partial class Payload
{
public const string _payloadKind = "$PayloadKind";
[JsonProperty(_payloadKind)]
protected abstract Discriminator PayloadKind { get; }
public Discriminator Kind => this.PayloadKind;
public enum Discriminator
{
COM = 0,
GAME = 1
}
}
partial class Payload
{
[JsonConverter(typeof(JsonSubTypes.JsonSubtypes), GAMEKIND)]
[JsonSubTypes.JsonSubtypes.KnownSubType(typeof(Game.Run), Discriminator.RUN)]
[JsonSubTypes.JsonSubtypes.KnownSubType(typeof(Game.Walk), Discriminator.WALK)]
public abstract partial class Game : Payload
{
protected override Payload.Discriminator PayloadKind => Payload.Discriminator.GAME;
public new enum Discriminator
{
RUN = 0,
WALK = 1
}
public const string GAMEKIND = "$GameKind";
[JsonProperty(GAMEKIND)]
protected abstract Discriminator GameKind { get; }
public new Discriminator Kind => this.GameKind;
}
}
partial class Payload
{
public abstract class Com : Payload
{
}
}
partial class Payload
{
partial class Game
{
public class Walk : Game
{
protected override Discriminator GameKind => Discriminator.WALK;
}
}
}
partial class Payload
{
partial class Game
{
public class Run : Game
{
protected override Discriminator GameKind => Discriminator.RUN;
}
}
}
}
Please make a release with the latest changes. It looks like the current build makes a nuget package that uses the same version that is already published even though the code has changed.
From AppVeyor Log
"Collecting artifacts...
Found artifact 'JsonSubTypes\bin\Release\JsonSubTypes.1.6.0.nupkg' matching 'JsonSubTypes***.nupkg' path
Uploading artifacts...
[1/1] JsonSubTypes\bin\Release\JsonSubTypes.1.6.0.nupkg (90,937 bytes)...100%
Deploying using NuGet provider
Publishing JsonSubTypes.1.6.0.nupkg to https://www.nuget.org/api/v2/package...Skipped (A package with ID 'JsonSubTypes' and version '1.6.0' already exists and cannot be modified.)
No packages were pushed.
Build success"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.