NSwag: The abstract class cannot be instantiated.

Hi,

I read this post: https://github.com/RSuter/NSwag/issues/1652 and I added a comment but my issue is not exactly the same even if the error is the same 😃.

So here is my code:

    public class GroupedResultDto<IItemDto, TEntity> : GroupedResultDto<IItemDto, TEntity, Guid>
        where TEntity : IEntityDto
    {
    }

    public class GroupedResultDto<IItemDto, TEntity, TPrimaryKey>
        where TEntity : IEntityDto<TPrimaryKey>
    {
        public int TotalCount { get; set; }
        public IEnumerable<GroupItemDto<IItemDto, TEntity, TPrimaryKey>> GroupItems { get; set; }
        public bool HasNext { get; set; }
    }
public class GroupItemDto<IItemDto, TEntity> : GroupItemDto<IItemDto, TEntity, Guid>
        where TEntity : IEntityDto
    {
    }

    public class GroupItemDto<IItemDto, TEntity, TPrimaryKey>
        where TEntity : IEntityDto<TPrimaryKey>
    {
        public IItemDto Item { get; set; }
        public IEnumerable<TEntity> Items { get; set; }
    }
public interface IItemDto
 {
     string Discriminator { get; }
 }
[JsonConverter(typeof(JsonInheritanceConverter))]
  [KnownType(typeof(ItemDto<string>))]
  [KnownType(typeof(ItemDto<UserDto>))]
  [KnownType(typeof(ItemDto<NotificationDto>))]
  public class ItemDto<T> : IItemDto
      where T : IComparable
  {
      public T Key { get; set; }
      public string Discriminator
      {
          get
          {
              return typeof(T).Name;
          }
      }

      public int CompareTo(object obj)
      {
          if (!(obj is ItemDto<T> item))
          {
              throw new InvalidCastException($"Not able to cast as '{typeof(T).Name}'.");
          }
          return Key.CompareTo(item.Key);
      }
  }

As Discriminator property is already declared in Interface IItemDto, so I updated your JsonInheritance converter like this:

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            try
            {
                _isWriting = true;

                var jObject = JObject.FromObject(value, serializer);
                //jObject[_discriminator] = JToken.FromObject(GetDiscriminatorValue(value.GetType()));
                writer.WriteToken(jObject.CreateReader());
            }
            finally
            {
                _isWriting = false;
            }
        }

As I read in your documentation (https://github.com/RSuter/NJsonSchema/wiki/Inheritance), I updated nswagstudio like this: image

image

But each time I try to get a groupedResultDto, I continue to have this error:

The abstract class ‘IItemDto’ cannot be instantiated.

Could you help me? Let me know if you need more details.

An thanks a lot for your work! Your app is really useful!

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Comments: 20 (6 by maintainers)

Most upvoted comments

Hi @ranouf / @RSuter any updates on this? In a perfect world I would like to use interfaces and not abstract classes as proposed.

Add to this, Swashbuckle seems to generate the correct documentation.

Yep, it’s more a Newtonsoft serialization problem at runtime (not NSwag/NJS), for more information (your solution), see https://github.com/RicoSuter/NJsonSchema/wiki/Inheritance

Simplest way is to just set GenerateAbstractSchemas to false, but we should still try to fix this. Please provide a sample where the TS code tries to instantiate an abstract class

@RSuter could you add me the right to publish a new branch to NJsonSchema please?

I created a branch who help to resolve this issue. Currently JsonInheritanceConverter take the Type.Name from Reflection, but there is a generic type, it generate “ItemDtoOfT`1” instead of “ItemDtoOfTUserDto” as we need in the TypeScriptApi Service. So I created this extension:

    public static class TypeExtensions
    {
        public static string GetGenericName(this Type t)
        {
            if (!t.IsGenericType)
                return t.Name;
            var genericTypeName = t.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
            var genericArgs = string.Join(
                ",",
                t.GetGenericArguments().Select(GetGenericName)
            );
            return genericTypeName + "Of" + genericArgs;
        }
    }

source: https://stackoverflow.com/a/2448918/6123422

and updated JsonInheritanceConverter with:

        public virtual string GetDiscriminatorValue(Type type)
        {
            return type.GetGenericName();
        }

currently the branch doesnt compile because of this: image

I think maybe this could fix it, but not sure, what do you do when you have this error?

#if !NETSTANDARD
            if (!t.IsGenericType)
                return t.Name;
            var genericTypeName = t.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
            var genericArgs = string.Join(
                ",",
                t.GetGenericArguments().Select(GetGenericName)
            );
            return genericTypeName + "Of" + genericArgs;
#else
            return t.Name;
#endif

let me know if it s ok for you?

I wanted to be exhaustive, sorry maybe I was too much 😃.

I created a repository to reproduce the issue: https://github.com/ranouf/TestNotification

You just need to launch the app, open the console in the browser, you will see this error:

core.js:14597 ERROR Error: The abstract class ‘AbstractItemDto’ cannot be instantiated. at Function.push…/src/app/services/api.services.ts.AbstractItemDto.fromJS (api.services.ts:227) at GroupItemDtoOfNotificationDtoAndGuid.push…/src/app/services/api.services.ts.GroupItemDtoOfNotificationDtoAndGuid.init (api.services.ts:176) at Function.push…/src/app/services/api.services.ts.GroupItemDtoOfNotificationDtoAndGuid.fromJS (api.services.ts:188) at GroupedResultDtoOfNotificationDto.push…/src/app/services/api.services.ts.GroupedResultDtoOfNotificationDtoAndGuid.init (api.services.ts:100) at GroupedResultDtoOfNotificationDto.push…/src/app/services/api.services.ts.GroupedResultDtoOfNotificationDto.init (api.services.ts:139) at Function.push…/src/app/services/api.services.ts.GroupedResultDtoOfNotificationDto.fromJS (api.services.ts:147) at MergeMapSubscriber.project (api.services.ts:68) at MergeMapSubscriber.push…/node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._tryNext (mergeMap.js:61) at MergeMapSubscriber.push…/node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._next (mergeMap.js:51) at MergeMapSubscriber.push…/node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)

What I would like:

  • Solution 1: nswag studio can generate correctly AbstractItemDto.fromJS using the discriminator from KnownTypeAttribute in AbstractItemDto.cs Example:
static fromJS(data: any): AbstractItemDto {
    data = typeof data === 'object' ? data : {};
    if (data.discriminator == "notificationDto") { //or itemDtoOfNotificationDto if you prefer, i will update the cs code file
      return ItemDtoOfNotificationDto.fromJS(data);
    }
    if (data.discriminator == "userDto") {
      return ItemDtoOfUserDto.fromJS(data);
    }
    if (data.discriminator == "string") {
      return ItemDtoOfString.fromJS(data);
    }
    throw new Error("The abstract class 'AbstractItemDto' cannot be instantiated.");
  }
  • Solution 2: nswag studio can allow me to overwrite AbstractItemDto.fromJS (but I prefer the solution 1)

Or another solution if you have other idea.

Thanks for your help @RSuter

The solution is (the fromJS part):

export abstract class AbstractItemDto implements IAbstractItemDto {

  constructor(data?: IAbstractItemDto) {
    debugger;
    if (data) {
      for (var property in data) {
        if (data.hasOwnProperty(property))
          (<any>this)[property] = (<any>data)[property];
      }
    }
  }

  init(data?: any) {
    if (data) {
    }
  }

  static fromJS(data: any): AbstractItemDto {
    data = typeof data === 'object' ? data : {};
    if (data.discriminator == "NotificationDto") {
      return ItemDtoOfNotificationDto.fromJS(data);
    }
    if (data.discriminator == "UserDto") {
      return ItemDtoOfUserDto.fromJS(data);
    }
    if (data.discriminator == "string") {
      return ItemDtoOfString.fromJS(data);
    }
    throw new Error("The abstract class 'AbstractItemDto' cannot be instantiated.");
  }

  toJSON(data?: any) {
    data = typeof data === 'object' ? data : {};
    return data;
  }
}

I dont know how your code works, but what I could imagine is during the generation of Typescripts services, when there is an abstract class, the generation tool must take the KnowTypeAttribute to generate the FromJs part.

Example:

    [JsonConverter(typeof(JsonInheritanceConverter))]
    [KnownType(typeof(ItemDto<string>))]
    [KnownType(typeof(ItemDto<UserDto>))]
    [KnownType(typeof(ItemDto<NotificationDto>))]
    [JsonSchemaFlatten]
    public abstract class AbstractItemDto
    {
        public abstract string Discriminator { get; }
    }

Maybe it s too custom? Let me know, I will try to adapt my code to your solution.

To continue to work untill a fix will be found (crossing the fingers), I choose a workaround to avoid the generation of AbstractItemDto, I added it here in Nswagstudio: image

but GroupItemDtoOfNotificationDtoAndGuid need as AbstractItemDto is the type of one of his property .

I will continue to investigate 😃

Any help will be welcome