Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

NullReferenceException

Open ljf88 opened this issue 1 year ago • 22 comments

Use Version:7.4.0

Source Class:
public class BaseResponse
{
   public string? TraceId { get; set; }
    public Guid InvoiceId { get; set; }
    public GatewayActionResultEnum ActionResult { get; set; }
    public string? ActionResultDescription { get; set; }
    public string? UniqueId { get; set; }
    public BaseRequest RawRequest { get; set; }
    public object RawResponse { get; set; }
    public dynamic? ResponseEntity { get; set; }
    public HttpStatusCode? StatusCode { get; set; }
    public List<Error> Errors { get; set; } = [];
    public bool IsRetry { get; set; }
}

public record Error(string Description, string? Code, string? Name);

public class PrepareResponse : BaseResponse
{
}

Target Class: public class BaseRes { public Guid InvoiceId { get; set; } public GatewayActionResultEnum ActionResult { get; set; } public string? ActionResultDescription { get; set; } public string? UniqueId { get; set; } public object RawRequest { get; set; } public string RawResponse { get; set; } public dynamic? ResponseEntity { get; set; } public bool IsFriendlyTip { get; set; } = false; public string? FriendlyTip { get; set; } public bool IsRetry { get; set; } public string? TraceId { get; set; } }

public class PrepareRes : BaseRes
{
    public Payment Payment { get; set; }
    public string? RedirectUrl { get; set; }
}

public partial class Payment { public Guid PaymentId { get; set; } public string GatewayPayerId { get; set; } = null!; public short? LastStatus { get; set; } public DateTime CreateTime { get; set; } public string GatewayPaymentId { get; set; } = null!; public string Currency { get; set; } = null!; public decimal Price { get; set; } public string? Memo { get; set; } public string GatewayPayerName { get; set; } = null!; }

test data: {"InvoiceId":"03f38e68-792a-48c0-a8ee-1d7877b2f7c9","ActionResult":1,"ActionResultDescription":null,"UniqueId":null,"RawRequest":null,"RawResponse":null,"ResponseEntity":null,"StatusCode":null,"Errors":[],"IsRetry":false,"TraceId":null}

call code: source.Adapt<TDestination>(TypeAdapterConfig.GlobalSettings) ; or source.Adapt<TDestination>();

Error message:"Object reference not set to an instance of an object." StackTrack:" 在 Mapster.TypeAdapter.Adapt[TDestination](Object source, TypeAdapterConfig config)"

description: When deleting the TraceId attribute in the baseResponse class or changing the TraceId attribute in the source and target classes from string? to string, the conversion works fine.

ljf88 avatar Jul 29 '24 03:07 ljf88

Supplementary Description: If you change the type of TraceId to e.g. int?, long?, the conversion is also normal

ljf88 avatar Jul 29 '24 03:07 ljf88

Please try with the latest prerelease version. If your issue is still occurring then please reopen this issue. Thanks.

stagep avatar Jan 07 '25 22:01 stagep

Having the exact same issue. Added a new string? property to both our entity and our dto with the same name, where there were already a dozen properties including other nullable strings, and suddenly Mapster gives a nullreferenceexception when doing the adapt. If I change the property to non nullable string it works.

Sounds like a bug in the codegen part, the exception detail points to a non specified closure method.

Edit: Tested up to 7.4.2-pre02 without success.

matteo-mosca avatar Apr 08 '25 13:04 matteo-mosca

@matteo-mosca You can write stack trace?

DocSvartz avatar Apr 08 '25 14:04 DocSvartz

@matteo-mosca You can write stack trace?

Sure, although I don't know how much help it can be.

System.NullReferenceException: Object reference not set to an instance of an object. at lambda_method1288(Closure, Object) at Mapster.TypeAdapter.Adapt[TDestination](Object source, TypeAdapterConfig config) at Mapster.TypeAdapter.Adapt[TDestination](Object source) at RedactedNamespace.CrudService.MapToDto(TEntity entity)

matteo-mosca avatar Apr 08 '25 16:04 matteo-mosca

This error is related to calling a method on an instance of object when it is null. So far I was able to get this, if I specify something like this in the configuration.

 TypeAdapterConfig<SimplePoco, Destination>
     .NewConfig()
     .Map(destination => destination.TraceId, source => source.TraceId.ToString());

//when  source.TraceId == null 

If I change the property to non nullable string it works.

Here did you mean source (Entity) ? You repository in this case (when Property is not "string?") return null or String.Empty ?

DocSvartz avatar Apr 08 '25 17:04 DocSvartz

This error is related to calling a method on an instance of object when it is null. So far I was able to get this, if I specify something like this in the configuration.

 TypeAdapterConfig<SimplePoco, Destination>
     .NewConfig()
     .Map(destination => destination.TraceId, source => source.TraceId.ToString());

//when  source.TraceId == null 

If I change the property to non nullable string it works.

Here did you mean source (Entity) ? You repository in this case (when Property is not "string?") return null or String.Empty ?

Our entities are POCO objects with no logic, and we use EF core. Here's a sample:

[Column(TypeName = "nvarchar(150)")]
public string Name { get; set; } = null!;

[Column(TypeName = "nvarchar(150)")]
public string? SupplierGroup { get; set; }

[Column(TypeName = "nvarchar(150)")]
public string? AlternativeName { get; set; }

[Column(TypeName = "varchar(150)")]
public string Address1 { get; set; } = null!;

[Column(TypeName = "varchar(150)")]
public string? Address2 { get; set; }

[Column(TypeName = "varchar(150)")]
public string? Address3 { get; set; }

[Column(TypeName = "varchar(150)")]
public string City { get; set; } = null!;

public int CountryId { get; set; }
public CountryEntity? Country { get; set; } = null!;

[Column(TypeName = "varchar(150)")]
public string State { get; set; } = null!;

[Column(TypeName = "varchar(150)")]
public string PostalCode { get; set; } = null!;

As you can see we use nullable strings as well as non nullable strings. We have dozens of entities and everything worked fine. Suddenly one dev added another nullable string to an entity:

[Column(TypeName = "nvarchar(150)")]
public string? UserNumber { get; set; }

And the error started occurring. Weird things: renaming the property to anything else doesn't change anything, error keeps happening. To "solve" the error, I have to either remove the property or make it a non nullable string.

As for your last question, we properly use nullable everywhere so for non nullable strings the repository returns string empty, but the repository is not involved in the problem. I unit tested this thing excluding the repository altogether and the error happens.

I already spent 2 full working days on this and I cannot explain this anymore than what I wrote here.

matteo-mosca avatar Apr 08 '25 19:04 matteo-mosca

Can you provide the minimal POCO, Entity (the Destination type) and mapping config that produces the exception? Thanks.

stagep avatar Apr 08 '25 20:04 stagep

everywhere so for non nullable strings the repository returns string empty

I think it's the other way around that's one of the reasons why it starts working when you change to a non null string.

When you found the problem, you didn't run these things (it's necessary to isolate the error at the stage of creating the mapper)?

var adaptMap = source.BuildAdapter().CreateMapExpression<Destination>();       // equal source.Adapt<Destination>()
var adaptMapToTarget = source.BuildAdapter().CreateMapToTargetExpression<Destination>();  // equal source.Adapt(destination)
var adaptMapProjection = source.BuildAdapter().CreateProjectionExpression<Destination>();  // equal source.ProjectToType<Destination>()

//  If it works without errors, then it is indeed related to the data processing by the mapping function.

Could you also provide DebugView, the version you are using from mapping?

Image

DocSvartz avatar Apr 09 '25 01:04 DocSvartz

how to resolve this problem? It's been almost a year now

refreshingqin avatar May 16 '25 03:05 refreshingqin

Hello @refreshingqin can you provide a non-working example and mapping configuration? The case I found that I described above does not fit the initial statements.

DocSvartz avatar May 16 '25 04:05 DocSvartz

@DocSvartz like this simple example

public class User
{
    public string? Id { get; set; }
    public string? Name { get; set; }

    public People People { get; set; }
}


public class People
{
    public string? Id { get; set; }
    public string? Name { get; set; }
}

public class UserDto
{
    public string? Id { get; set; }
    public string? Name { get; set; }

    public PeopleDto People { get; set; }
}

public class PeopleDto
{
    public string? Id { get; set; }
    public string? Name { get; set; }
}

then use user map to userDto 
var user = new User();
var userDto= user.Adapt<UserDto>();

refreshingqin avatar May 16 '25 05:05 refreshingqin

@refreshingqin In your case it is due to the use of Nullable Annotation for reference types. all member in #nullable enable section with out ? mark as never not null . For this reason, no additional Null check is created.

you can use

public People? People { get; set; }

or #nullable disable in file where where do you define Class User if property People can receive a null value.

In one of the new PRs I changed this behavior. In one of the new PRs i changed this behavior to unconditionally create a null check for reference types

DocSvartz avatar May 16 '25 07:05 DocSvartz

@DocSvartz after my testing ,If only one Nullable type and one Object type are used in the main class, there is no problem. However, if there are multiple Nullables and one Object, a bug may occur

this sample is ok

  public class User
  {
      public string? Id { get; set; }
    

      public People People { get; set; }
  }

`
also pass test
`  
public class User
 {
     public string? Id { get; set; }
     public string? Name { get; set; }
 
 }
`
but next will be thow exception 
 public class User
 {
     public string? Id { get; set; }
     public string? Name { get; set; }

     public People People { get; set; }
 }

Please verify , thanks

refreshingqin avatar May 16 '25 07:05 refreshingqin

@refreshingqin Yes, this looks like some compiler optimization. 🤔

in #nullable enable context:

this case

public class User
{
public string? Id { get; set; }
public People People { get; set; }

Property People does not get an automatic nullable annotation

in this case

public class User
{
public string? Id { get; set; }
public string? Name { get; set; }
public People People { get; set; }
}

Property People will have a nullable annotation NullableAttribute(1)

DocSvartz avatar May 16 '25 09:05 DocSvartz

@DocSvartz i don't think in case of People .because of the first demo can pass the test

`

public class User { public string? Id { get; set; } public People People { get; set; } }

`

but the second demo can't pass the test

refreshingqin avatar May 21 '25 05:05 refreshingqin

@refreshingqin In #nullable enable context both cases should return fail. Because a non-nullable property should not return null.

public class User
 {
     public string? Id { get; set; }
     public string? Name { get; set; }

     public People People { get; set; } // this return null unless you create it yourself  = new ()
 }

DocSvartz avatar May 21 '25 09:05 DocSvartz

but i use People? People also throw exception ,if User just exist only one Propery is string? type ,can't throw exception . but more one and exist Object Type Property will throw exception .could you fixed it?

refreshingqin avatar May 21 '25 09:05 refreshingqin

Does this also give an exception? Perhaps People itself also has properties that are null when they shouldn't be?

 public class User
 {
     public string? Id { get; set; }
     public string? Name { get; set; }
     public People? People { get; set; } // This works for me without errors.
 }

Something like here

 public class User
 {
     public string? Id { get; set; }
     public string? Name { get; set; }
     public People? People { get; set; } = new People();
 }


 public class People
 {
     public string? Id { get; set; }
     public string? Name { get; set; }

     public PeopleExternal External { get; set; } // The error will be for the same reason, but here
 }

but more one and exist Object Type Property will throw exception .could you fixed it?

If the reason is really only this, then there is a fix.

DocSvartz avatar May 21 '25 09:05 DocSvartz

got it ! Thanks !

refreshingqin avatar May 22 '25 02:05 refreshingqin

@DocSvartz @refreshingqin I've also encountered the same problem recently. I'm sorry, but I don't know how to solve it? It means adding a new() on property?

liuliucn avatar May 24 '25 14:05 liuliucn

@liuliucn This is relevant for types defined in #nullable enable context. In source property that is not explicitly marked as nullable People must not contain null.

Does it mean adding a new() on property?

Yes, that might help.

If you have a different situation, can you provide an example.

DocSvartz avatar May 24 '25 15:05 DocSvartz