0

I wrote a test programe with .NET 6

        static void Main(string[] args) {
            JsonSerializerOptions options = new JsonSerializerOptions() {
                Converters = {
                    new JsonStringEnumConverter(),
                    new JsonStringAbilityEffectFactoryConverter()
                }
            };

            AbilityTemplate dictionary = JsonSerializer.Deserialize<AbilityTemplate>(@"
                {
                    ""Id"": ""StandardShot"",
                    ""Price"": 10,
                    ""Size"": 1,
                    ""Rarity"": ""Common"",
                    ""AbilityEffectFactory"": {
                        ""Id"":""StandardShotEffectFactory""
                    },
                    ""PropertyBasicValues"": {
                        ""CoolDownRate"": 5,
                        ""CastRate"": ""Infinity""
                    }
                }
            ");
        }
    }

    internal class JsonStringAbilityEffectFactoryConverter : JsonConverter<AbilityEffectFactory> {
        public override AbilityEffectFactory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
            string id = "";//Break point here. This method is just casually implemented because I am merely especting this method to be called.
            while (reader.Read()) {
                id = reader.GetString();
            }
            return AbilityEffectFactory.Dictionary[id];
        }

        public override void Write(Utf8JsonWriter writer, AbilityEffectFactory value, JsonSerializerOptions options) {
            throw new NotImplementedException();
        }
    }

And my AbilityTemplate Class is like this:

    public class AbilityTemplate : RarityObject {
        public AbilityTemplate(string id, int price, int size, Rarity rarity, AbilityEffectFactory abilityEffectFactory, in Ability.PropertyCollection propertyBasicValues) {
            Id = id;
            Price = price;
            Size = size;
            Rarity = rarity;
            AbilityEffectFactory = abilityEffectFactory;
            PropertyBasicValues = propertyBasicValues;
        }

        internal static readonly Dictionary<string, AbilityTemplate> dictionary = new Dictionary<string, AbilityTemplate>();
        public static IReadOnlyDictionary<string, AbilityTemplate> Dictionary => dictionary;
        public string Id { get; }
        public int Price { get; }
        public override Rarity Rarity { get; }
        public int Size { get; }
        public AbilityEffectFactory AbilityEffectFactory { get; }
        public Ability.PropertyCollection PropertyBasicValues { get; }
    }

The parent class RarityObject is just a simple abstract class with one field which is overrided:

    public abstract class RarityObject {
        public abstract Rarity Rarity { get; }
    }

However, when the code executes, I was thrown exception:

System.InvalidOperationException: 'Each parameter in the deserialization constructor on type 'Enigma.Game.AbilityTemplate' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.'

I cannot understand how can this exception occur. Each field written in Json text is already exactly matched to properties of my AbilityTemplate class.

Update0 My AbilityEffectFactory is an abstract class, maybe thats what went wrong.

Update1 I modified the Converter to be like this:

    internal class JsonStringAbilityTemplateConverter : JsonConverter<AbilityTemplate> {
        public override AbilityTemplate Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
            string id = "";//Break point
            while (reader.Read()) {
                id = reader.GetString();
            }
            return new AbilityTemplate();
        }

        public override void Write(Utf8JsonWriter writer, AbilityTemplate value, JsonSerializerOptions options) {
            throw new NotImplementedException();
        }
    }

But I still got exception:

System.NotSupportedException: 'Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Enigma.Game.AbilityEffectFactory'. Path: $.AbilityEffectFactory | LineNumber: 6 | BytePositionInLine: 45.'

And the break point wasn't hit.How can this be posible as the system already read to line 6?

dbc
  • 104,963
  • 20
  • 228
  • 340
martinrhan
  • 362
  • 1
  • 18
  • 1
    If you change `{ get; }` for all your properties to `{ get; set }`, is it fixing the issue? – Lajos Arpad Jan 08 '22 at 12:44
  • @LajosArpad but this class should be immutable – martinrhan Jan 08 '22 at 13:05
  • Is it fixing the issue? We should know whether adding a setter would fix it, even if you do not apply that for your solution. – Lajos Arpad Jan 08 '22 at 13:06
  • Which version of .net-core are you using? Looks like it should be possible in .net-core 5+ https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-6-0 – haldo Jan 08 '22 at 13:21
  • @LajosArpad It doesn't work – martinrhan Jan 08 '22 at 13:58
  • @haldo I am using .NET 6 – martinrhan Jan 08 '22 at 13:58
  • Does this answer your question? [Deserialization of reference types without parameterless constructor is not supported](https://stackoverflow.com/questions/59198417/deserialization-of-reference-types-without-parameterless-constructor-is-not-supp) – phuzi Jan 08 '22 at 14:16

2 Answers2

0
  1. You have a bug in your class - Rarity is a string in json, and can not be a Rarity class property

  2. When you serialize an object you need only property getter, property setter maybe optional, but when you deserialize object you need a setter, at least in Text.Json, since text json doesn't use constructor properly. You can try Newtosoft.Json since it can use a constructor.

  3. Never use interface or abstract classes properties if you want to deserialize json, since nobody knows what class to use in order to create an object. An interface or abstract class can have thousands different implementations

  4. I make some changes to your class by replacing abstract class by a concrete dictionary<string, object> class. It is working using Newtonsoft.Json. You only have to replace objects by concrete classes.

AbilityTemplate abilityTemplate = JsonConvert.DeserializeObject<AbilityTemplate>(json);

class

public class AbilityTemplate 
{
    [JsonConstructor]
    public AbilityTemplate(string id, int price, int size, string rarity, Dictionary<string, object> abilityEffectFactory, in Dictionary<string,object> propertyBasicValues)
    {
        Id = id;
        Price = price;
        Size = size;
        Rarity = rarity;
        AbilityEffectFactory = abilityEffectFactory;
        PropertyBasicValues = propertyBasicValues;
    }

    internal static readonly Dictionary<string, AbilityTemplate> dictionary = new Dictionary<string, AbilityTemplate>();
    public static IReadOnlyDictionary<string, AbilityTemplate> Dictionary => dictionary;
    public string Id { get; }
    public int Price { get; }
    public string Rarity { get; }
    public int Size { get; }
    public Dictionary<string,object> AbilityEffectFactory { get; }
    public Dictionary<string,object> PropertyBasicValues { get; }
}
  1. IMHO, you don't need the special converter. If you want to convert json to something weird, you can place all the code in the constructor.
Serge
  • 40,935
  • 4
  • 18
  • 45
0

I finally got this done by not using JsonConverter. As shown bellow, I wrote intermediate classes that can be converted to aimed class.

    public static partial class EntityLoader {
        private class AbilityTemplateJsonObject {
            public string Id { get; set; }
            public int Price { get; set; }
            public int Size { get; set; }
            public string Rarity { get; set; }
            public string AbilityEffectFactory { get; set; }
            public AbilityPropertyCollectionJsonObject PropertyBasicValues { get; set; }
            public AbilityTemplate ConvertToAbilityTemplate() {
                return new AbilityTemplate(Id, Price, Size, Enum.Parse<Rarity>(Rarity), Game.AbilityEffectFactory.Dictionary[AbilityEffectFactory], PropertyBasicValues.ConvertToAbilityPropertyCollection());
            }
        }
        private class AbilityPropertyCollectionJsonObject {
            public double Strength { get; set; }
            public double FireRate { get; set; }
            public double PerFireQuantity { get; set; }
            public double CoolDownRate { get; set; }
            public double CastRate { get; set; }
            public double Range { get; set; }
            public double ExplosionRange { get; set; }
            public double ExplosionStrength { get; set; }
            public double ManaConservation { get; set; }
            public double ManaStorage { get; set; }
            public Ability.PropertyCollection ConvertToAbilityPropertyCollection() {
                return new Ability.PropertyCollection() {
                    [Ability.PropertyType.Strength] = Strength,
                    [Ability.PropertyType.FireRate] = FireRate,
                    [Ability.PropertyType.PerFireQuantity] = PerFireQuantity,
                    [Ability.PropertyType.CoolDownRate] = CoolDownRate,
                    [Ability.PropertyType.CastRate] = CastRate,
                    [Ability.PropertyType.Range] = Range,
                    [Ability.PropertyType.ExplosionRange] = ExplosionRange,
                    [Ability.PropertyType.ExplosionStrength] = ExplosionStrength,
                    [Ability.PropertyType.ManaConservation] = ManaConservation,
                    [Ability.PropertyType.ManaStorage] = ManaStorage
                };
            }
        }

martinrhan
  • 362
  • 1
  • 18