nozzlegear / ShopifySharp

ShopifySharp is a .NET library that helps developers easily authenticate with and manage Shopify stores.
https://nozzlegear.com/shopify-development-handbook
MIT License
742 stars 309 forks source link

Unable to create a new product (deserialization error with object not found) #968

Open pmartinciibo opened 9 months ago

pmartinciibo commented 9 months ago

I have successfully retrieved collections of orders, customers and products. I am now attempting to sync my products with an internal database (database > Shopify). I can update a product no problem, but creating a new one always throws an exception.

'Object reference not set to an instance of an object.' that is being thrown at {T DeserializeWithNewtonsoft[T](System.String, System.String, System.Nullable`1[Newtonsoft.Json.DateParseHandling])}.

I thought maybe I was leaving a required field null, so populated just about everything I can think of prior to calling ProductService.CreateAsync(), but no change.

My method to create the ShopifySharp.Product is:

    public ShopifySharp.Product GenerateShopifyProduct(IvyProduct ivyProduct)
    {
        var shopifyProduct = new ShopifySharp.Product
        {
            Handle = ivyProduct.Identifier.ToString(),
            Title = ivyProduct.Title,
            Status = "active",
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now,
            PublishedAt = DateTime.Now,
            Vendor = "Ciibo Ivy",
            TemplateSuffix = "",
            PublishedScope = "",
            Tags = "",
            //public IEnumerable<ProductOption> Options
            //public IEnumerable<MetaField> Metafields
        };

        //Body
        shopifyProduct.BodyHtml = objectManager.IvyProductManager.GenerateContent(ivyProduct,;

        //Variant
        var variantList = new List<ShopifySharp.ProductVariant>();
        variantList.Add(new ShopifySharp.ProductVariant
        {
            SKU = ivyProduct.Identifier.ToString(),
            CompareAtPrice = (decimal)ivyProduct.AmazonPrice,
            Price = (decimal)ivyProduct.ListingPrice,
            Title = ivyProduct.Title,
            InventoryQuantity = 1,
            WeightUnit = "lb",
            Weight = (decimal)(ivyProduct.ShippingDetails.Weight * GRAMS_PER_POUND),
            InventoryPolicy = "deny",
            FulfillmentService = "manual",
        });
        shopifyProduct.Variants = variantList;

        //Create the list of images
        var images = new List<ShopifySharp.ProductImage>();
        foreach (var image in ivyProduct.Product.images)
        {
            images.Add(new ShopifySharp.ProductImage { Src = image.link });
        }
        shopifyProduct.Images = images;

        //Product type (category)
        shopifyProduct.ProductType = "MISCELLANEOUS";

        return shopifyProduct;
    }

Which I then pass into:

    public async Task<ShopifySharp.Product> CreateProductAsync(ShopifySharp.Product shopifyProduct)
    {
        //Create the service object
        var service = new ShopifySharp.ProductService(Domain, AccessToken);

        //Call the method
        var product = await service.CreateAsync(shopifyProduct); //Throws exception
        return product;
    }

And the calling code is:

                        var shopifyProduct = shopifyManager.GenerateShopifyProduct(ivyProduct);
                        shopifyProduct = shopifyManager.CreateProduct(shopifyProduct); //THros exception

(Note: relative to a past similar issue, I have made sure to use *.myshopify.com as the domain to avoid a redirect).

pmartinciibo commented 9 months ago

Here is the stack trace:

System.NullReferenceException HResult=0x80004003 Message=Object reference not set to an instance of an object. Source=ShopifySharp StackTrace: at ShopifySharp.Infrastructure.Serializer.DeserializeWithNewtonsoft[T](String json, String rootElementPath, Nullable1 dateParseHandlingOverride) at ShopifySharp.ShopifyService.<>c__DisplayClass25_01.<b0>d.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at ShopifySharp.DefaultRequestExecutionPolicy.d01.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at ShopifySharp.ShopifyService.<ExecuteRequestCoreAsync>d__251.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at ShopifySharp.ShopifyService.d27`1.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at ShopifySharp.ProductService.d5.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at AmazonDsp.Services.Objects.Ivy.Shopify.ShopifyManager.d__39.MoveNext() in C:\Development\AmazonDsp\AmazonDsp.Services.Objects\Ivy\Shopify\ShopifyManager.cs:line 1241

nozzlegear commented 9 months ago

Thanks @pmartinciibo! Would it be possible to debug the request and see if you can get the raw response body? It should be a property on the exception:

try
{
  // your code that throws an exception here
}
catch (ShopifyException ex)
{
  Console.WriteLine(ex.RawBody);
  throw;
}

That should give us the exact string that Shopify is returning, which will give a hint as to what can't be deserialized.

pmartinciibo commented 9 months ago

It doesn't look like a ShopifyException is being thrown. Exception

nozzlegear commented 9 months ago

Oh, right, my bad. It's a null reference problem so it won't throw a ShopifyException. We'll need to look at the body before ShopifySharp tries to deserialize it, which is a little tricky. I'm going to write some test code and get back to you!

Jason-Powell-Sync commented 7 months ago

I am experiencing the same issue. I am not familiar with the code, but this is what is happening:

I am trying to create a product, the baseRequestMessage is the request to create (the POST), but it does a GET request first for some reason. image

The API responds successfully, but the json returned has "products" as the root node, not "product", which is what is being sent through: image

When deserializing, the node is not found, jToken is null and calling ToObject results in the null ref exception.

image

nozzlegear commented 7 months ago

Sorry this slipped my mind. Thanks a ton @Jason-Powell-Sync! That helps me a lot. You said it's doing a GET request first? That's very strange and should not be happening, but it would explain why "products" is being returned as the GET request would mean Shopify is returning a list of products.

Jason-Powell-Sync commented 7 months ago

I'm digging into this further on my side still. I think it is doing a POST, but getting the GET response back. I am getting the same result on Postman.

Jason-Powell-Sync commented 7 months ago

This is an issue on the Shopify side. This shows the exact issue I am having: https://community.shopify.com/c/shopify-scripts/product-post-method-is-returning-status-200-but-don-t-create-a/td-p/2232624

nozzlegear commented 7 months ago

Thanks for investigating, it's unfortunate the person in that forum post never got an answer from Shopify staff. I'd be curious to see the request and response headers if you have them, with your access-token and shop domain redacted. Maybe there's something in there that can clue us in as to what's going on.

Jason-Powell-Sync commented 7 months ago

I resolved my issue, it was because of Shopify Domains.

I was using the first one, the Primary, when I switched to the other one, it worked. @pmartinciibo do you have multiple domains?

image

BradMJustice commented 7 months ago

Jumping in to second that using the second URL (redirects to ) also worked for me!

nozzlegear commented 7 months ago

Glad it's working for you guys! Just so I understand the solution, do you mind answering some questions I have if you have time?

  1. The domain that wasn't working was a *.myshopify.com domain?
  2. Using the second URL in ShopifySharp, the one that says it will redirect to a *.myshopify.com domain, is the one that actually works?
  3. If they're both *.myshopify.com domains, which one was the original one for the store?
BradMJustice commented 7 months ago

Glad it's working for you guys! Just so I understand the solution, do you mind answering some questions I have if you have time?

  1. The domain that wasn't working was a *.myshopify.com domain?
  2. Using the second URL in ShopifySharp, the one that says it will redirect to a *.myshopify.com domain, is the one that actually works?
  3. If they're both *.myshopify.com domains, which one was the original one for the store?

The one that was not working was my branded, primary domain (E.G., awesomestore.myshopfiy.com). The one that did work was more like a GUID (E.G. fffffff-f.myshopify.com). I don't remember for sure, but I suppose the latter was the original one, and I think I added the custom domain later.

Jason-Powell-Sync commented 7 months ago

Glad it's working for you guys! Just so I understand the solution, do you mind answering some questions I have if you have time?

  1. The domain that wasn't working was a *.myshopify.com domain?
  2. Using the second URL in ShopifySharp, the one that says it will redirect to a *.myshopify.com domain, is the one that actually works?
  3. If they're both *.myshopify.com domains, which one was the original one for the store?

The one that was not working was my branded, primary domain (E.G., awesomestore.myshopfiy.com). The one that did work was more like a GUID (E.G. fffffff-f.myshopify.com). I don't remember for sure, but I suppose the latter was the original one, and I think I added the custom domain later.

Mine was the exact same, the original DNS was random letters and numbers (which works) and the primary custom domain was added later (which does not work).