Search code examples
c#linqasp.net-coremodel-binding

How to do a comparison operator on dynamic and string


I have two http request that are binded to the following models:

    public class ShopifyAPI
{
    public class ShopifyAPIAuth
    {
        [Required]
        public required string AccessToken { get; set; }

        [Required]
        public required string Key { get; set; }

        [Required]
        public required string Secret { get; set; }
    }


    public class ShopifyOrderResponse
    {
        [Required]
        [JsonPropertyName("orders")]
        public required List<ShopifyOrder>? Orders { get; set; }
    }

    public class ShopifyOrder
    {
        [Required]
        [JsonPropertyName("id")]
        public long Id { get; set; }

        [Required]
        [JsonPropertyName("order_number")]
        public required long OrderNumber { get; set; }

        [Required]
        [JsonPropertyName("financial_status")]
        public required string PaymentStatus { get; set; }

        [Required]
        [JsonPropertyName("shipping_address")]
        public required Address ShippingAddress { get; set; }

        [Required]
        [JsonPropertyName("billing_address")]
        public required Address BillingAddress { get; set; }

        [Required]
        [JsonPropertyName("line_items")]
        public required List<Product> Products { get; set; }

    }

    public class Address
    {
        [Required]
        [JsonPropertyName("first_name")]
        public required string FirstName { get; set; }

        [Required]
        [JsonPropertyName("last_name")]
        public required string LastName { get; set; }

        [Required]
        [JsonPropertyName("address1")]
        public required string Address1 { get; set; }

        [JsonPropertyName("address2")]
        public string? Address2 { get; set; }

        [Required]
        [JsonPropertyName("city")]
        public required string City { get; set; }

        [Required]
        [JsonPropertyName("province")]
        public required string State { get; set; }

        [Required]
        [JsonPropertyName("zip")]
        public required string Zipcode { get; set; }

        [Required]
        [JsonPropertyName("country")]
        public required string Country { get; set; }

        [JsonPropertyName("phone")]
        public string? Phone { get; set; }
    }

    public class Product
    {
        [JsonPropertyName("product_id")]
        public long? Id { get; set; }

        [JsonPropertyName("title")]
        public string? ProductName { get; set; }

        [JsonPropertyName("variant_id")]
        public long? VariationId { get; set; }

        [JsonPropertyName("variant_title")]
        public string? VariationName { get; set; }

        [JsonPropertyName("quantity")]
        public long? Quantity { get; set; }
    }
}

and

public class WoocommerceAPI
{
    public class WoocommerceAPIAuth
    {
        [Required]
        public required string Key { get; set; }

        [Required]
        public required string Secret { get; set; }
    }

    public class WoocommerceOrder
    {
        [Required]
        [JsonPropertyName("id")]
        public long Id { get; set; }

        [Required]
        [JsonPropertyName("status")]
        public required string Status { get; set; }

        [Required]
        [JsonPropertyName("shipping")]
        public required Address ShippingAddress { get; set; }

        [Required]
        [JsonPropertyName("billing")]
        public required Address BillingAddress { get; set; }

        [Required]
        [JsonPropertyName("line_items")]
        public required List<Product> Products { get; set; }

        [Required]
        [JsonPropertyName("meta_data")]
        public required List<MetaData> MetaDatas { get; set; }

    }

    public class Address
    {
        [Required]
        [JsonPropertyName("first_name")]
        public required string FirstName { get; set; }

        [Required]
        [JsonPropertyName("last_name")]
        public required string LastName { get; set; }

        [Required]
        [JsonPropertyName("address_1")]
        public required string Address1 { get; set; }

        [JsonPropertyName("address_2")]
        public string? Address2 { get; set; }

        [Required]
        [JsonPropertyName("city")]
        public required string City { get; set; }

        [Required]
        [JsonPropertyName("state")]
        public required string State { get; set; }

        [Required]
        [JsonPropertyName("postcode")]
        public required string Zipcode { get; set; }

        [Required]
        [JsonPropertyName("country")]
        public required string Country { get; set; }

        [JsonPropertyName("phone")]
        public string? Phone { get; set; }
    }

    public class Product
    {
        [JsonPropertyName("product_id")]
        public long? Id { get; set; }

        [JsonPropertyName("name")]
        public string? ProductName { get; set; }

        [JsonPropertyName("variation_id")]
        public long? VariationId { get; set; }

        [JsonPropertyName("quantity")]
        public long? Quantity { get; set; }
    }

    public class MetaData
    {
        [Required]
        [JsonPropertyName("id")]
        public required dynamic Id { get; set; }

        [Required]
        [JsonPropertyName("key")]
        public required dynamic Key { get; set; }

        [Required]
        [JsonPropertyName("value")]
        public required dynamic Value { get; set; }

    }
}

I use these models in the following controller:

    [HttpPost(Name = "PostDataExport")]
    [EnableRateLimiting("api")]
    public async Task<IActionResult> Post()
    {
        Task<List<ShopifyOrder>> importShopifyOrders = _shopifyService.GetOrders("https://dirtdudesutv.myshopify.com", _shopifyAPIAuth.AccessToken);
        Task<List<WoocommerceOrder>> importWoocommerceOrders = _woocommerceService.GetOrders("https://www.aaaprintco.com", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_woocommerceAPIAuth.Key}:{_woocommerceAPIAuth.Secret}")));

        List<Task> tasks = new List<Task> { importShopifyOrders, importWoocommerceOrders };
        await Task.WhenAll(tasks);

        foreach (var order in importShopifyOrders.Result)
        {
            bool alreadyImportedOrder = importWoocommerceOrders.Result.Any(o => o.MetaDatas.Any(m =>(m.Key == "_shopify_order_id" && m.Value.ToString() == order.Id.ToString())));

            if(alreadyImportedOrder)
            {
                importShopifyOrders.Result.Remove(order);
            }
        }

        return Ok(importWoocommerceOrders.Result);
    }
}

The purpose of this controller is I pull all shopify orders and all woocommerce orders. I then need to find which shopify orders are already in woocommerce orders and if the order exists as shown in the foreach loop then it will delete it from the list. The issue is to check if the order exists in woocommerce there is a meta data field (as shown in the model) that has different meta data items. One of them is _shopify_order_id. So I nested some any LINQ statements to try and loop through and see if it exists. The issue I think is i use dynamic for the types since the meta data values are different types it seems. So when I run this I get an error at the meta data comparison:

    Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Operator '==' cannot be applied to operands of type 'System.Text.Json.JsonElement' and 'string'
   at CallSite.Target(Closure, CallSite, Object, String)

I am thinking there is an issue comparing string to dynamic?


Solution

  • Using ToString on both sides to do a comparison can be tricky. It would be better to use string.Equals which requires that the other item be a string:

    "_shopify_order_id".Equals(m.Key) && (m.Value.ToString() == order.Id.ToString())
    

    I would also be caution about using ToString on the order ID comparison - if you had an order with an ID value "123" and a key with value 123, the comparison would be true even if they were of different types.

    So you might also want:

    "_shopify_order_id".Equals(m.Key) && order.Id.Equals(m.Value)