VBA-tools / VBA-Web

VBA-Web: Connect VBA, Excel, Access, and Office for Windows and Mac to web services and the web
http://vba-tools.github.io/VBA-Web/
MIT License
2.01k stars 494 forks source link

array in body parameter (or nested body parameter) #331

Closed Ma-Trim closed 6 years ago

Ma-Trim commented 6 years ago

Hello, how would I do the following- image

This is the code I have started-

Dim Client As New WebClient
Client.BaseUrl = "https://link here"
Dim Request As New WebRequest
Request.Resource = "listings"
Request.Method = WebMethod.Httppost
Request.Format = WebFormat.Json
Request.AddHeader "Authorization", m_sToken
Request.AddHeader "Content-Type", "application/hal+json"
Request.AddHeader "Accept", "application/hal+json"
Request.AddHeader "Accept-Version", "3.0"
Request.AddBodyParameter "make", sMake
Request.AddBodyParameter "model", sModel
Request.AddBodyParameter "categories", sCategory
Request.AddBodyParameter "condition", sCondition
Request.AddBodyParameter "description", sDescription
Request.AddBodyParameter "finish", vFinish
Ma-Trim commented 6 years ago

I found this- https://github.com/VBA-tools/VBA-Web/issues/63 and looked at this example:

' Dim Items As New Collection ' Dim Item As Dictionary ' Dim Id As Long ' ' For Id = 1 To 2 ' Set Item = New Dictionary ' Item("iditem") = Id ' Item("amount") = 1 ' Items.Add Item ' Next Id ' ' Request.AddBodyParameter "id", 5633 ' Request.AddBodyParameter "items", Items

and came up with this try- (haven't ran it yet) :)

    Dim Prices As New Collection
    Dim Price As Dictionary
        Set Price = New Dictionary
        Price("amount") = dPrice
        Price("currency") = m_sCurrency
        Prices.Add Price
    Request.AddBodyParameter "prices", Price

BUT- now I need to do this as well, and am really not sure what to do!

"shipping": { "rates": [ { "rate": { "amount":"10.00" "currency": "USD" }, "region_code": "US_CON" }, { "rate": { "amount":"20.00" "currency": "USD" }", //USD "region_code": "Non_US_CON" }, ] "local": true } }

zgrose commented 6 years ago

If the desired output is at the bottom of your last message (fixing typos) that could be achieved via:

Public Sub BuildDeepDictionary()

    Dim fullObj As Dictionary
    Set fullObj = New Dictionary

    Dim shippingObj As Dictionary
    Set shippingObj = New Dictionary

    Dim rates As Collection
    Set rates = New Collection

    Dim aRate As Dictionary
    Dim anAmount As Dictionary

    Set anAmount = New Dictionary
    anAmount("amount") = "10.00"
    anAmount("currency") = "USD"

    Set aRate = New Dictionary
    Set aRate("rate") = anAmount
    aRate("region_code") = "US_CON"

    rates.Add aRate

    Set anAmount = New Dictionary
    anAmount("amount") = "20.00"
    anAmount("currency") = "USD"

    Set aRate = New Dictionary
    Set aRate("rate") = anAmount
    aRate("region_code") = "Non_US_CON"

    rates.Add aRate

    Set shippingObj("rates") = rates
    shippingObj("local") = True

    Set fullObj("shipping") = shippingObj

    Debug.Print ConvertToJson(fullObj)

End Sub
{"shipping":{"rates":[{"rate":{"amount":"10.00","currency":"USD"},"region_code":"US_CON"},{"rate":{"amount":"20.00","currency":"USD"},"region_code":"Non_US_CON"}],"local":true}}
Ma-Trim commented 6 years ago

Thank you! Does my attempt for the first one look about right? (for the Price, amount, currency?)

zgrose commented 6 years ago

You are referring to:

    Dim Prices As New Collection
    Dim Price As Dictionary
        Set Price = New Dictionary
        Price("amount") = dPrice
        Price("currency") = m_sCurrency
        Prices.Add Price
    Request.AddBodyParameter "prices", Price

So Price will be [ {"amount": 100, "currency": "xxx" } ]

and you're setting the prices parameter so you'll get

prices="something here"

in your request. But I have to admit, I don't use the Request bits of this library, just the JSON bits. So I'm not sure if you need Request.AddBodyParameter "prices", Price or Request.AddBodyParameter "prices", ConvertToJson(Price)

to fill the form properly.

Usually these APIs either take forms or a JSON body as a whole, so you may have to do:

Dim r As New WebRequest
Set r.Body = fullObj

Where you pass in your whole dictionary (or collection) to the request.

(edit) If they expect the price as a string (e.g. "10.00") and not a number (e.g. 10.00) you may have to change the datatype of dPrice or CStr() it to get the value in a format they are expecting.

Ma-Trim commented 6 years ago

Here is the JSON in it's entirety, this is for a POST... maybe I am more lost than I thought... :)

curl -XPOST -H "Authorization: Bearer [oauth_token]" -H "Accept-Version: 3.0" -H "Content-Type: application/hal+json" https://api.reverb.com/api/listings --data ' { "make": "Fender", "model": "Stratocaster", "categories": [{ "uuid": "get these values from /api/categories/flat" }], "condition": { "uuid": "get these from /api/listing_conditions }, "photos": [ "http://i.stack.imgur.com/Sv4BC.png", "http://i.stack.imgur.com/foo.png" ], "description": "Awesome guitar", "finish": "Sunburst", "price": { "amount": "5000.00", "currency": "USD }, "title": "my favorite fender stratocaster", "year": "1960s", "sku": "12345", "has_inventory": true, "inventory": 5, "handmade": true, "location": { // will default to shop address if not passed in "country_code": "USA", "region": "IL", "locality": "Chicago" }, "exclusive_channel": "none" // for Reverb Sites customers only. Set to "seller_site", "reverb", or "none" or leave blank "shipping_profile_id": 123, // instead of shipping rates "shipping": { // only use this if not using shipping profiles "rates": [ { "rate": { "amount":"10.00" "currency": "USD" }, "region_code": "US_CON" // Continental US }, { "rate": { "amount":"20.00" "currency": "USD" }", //USD "region_code": "XX" // Everywhere Else }, ] "local": true }
} '

Ma-Trim commented 6 years ago

This is the code I have so far-

Dim Client As New WebClient
Client.BaseUrl = "https://api.reverb.com/api/"
Dim Request As New WebRequest
Request.Resource = "listings"
Request.Method = WebMethod.Httppost
Request.Format = WebFormat.Json
Request.AddHeader "Authorization", m_sToken
Request.AddHeader "Content-Type", "application/hal+json"
Request.AddHeader "Accept", "application/hal+json"
Request.AddHeader "Accept-Version", "3.0"
Request.AddBodyParameter "make", sMake
Request.AddBodyParameter "model", sModel
Request.AddBodyParameter "categories", sCategory
Request.AddBodyParameter "condition", sCondition
Request.AddBodyParameter "description", sDescription
Request.AddBodyParameter "finish", vFinish
Dim Prices As New Collection
Dim Price As Dictionary
    Set Price = New Dictionary
    Price("amount") = dPrice
    Price("currency") = m_sCurrency
    Prices.Add Price
Request.AddBodyParameter "prices", Price
Request.AddBodyParameter "title", sTitle
Request.AddBodyParameter "year", vYear
Request.AddBodyParameter "sku", sSKU
Request.AddBodyParameter "has_inventory", sHasInventory
Request.AddBodyParameter "inventory", dInventoryQTY
Request.AddBodyParameter "handmade", sHandMade```
Ma-Trim commented 6 years ago

When you are looking at JSON, is there a way to know when it's a dictionary and when it's a collection? Is there a good tutorial online you can point us too? I want to learn how to understand what I am trying to do, this is all new to me. VBA I am pretty good at, but the API thing is brand new. Thanks again for your excellent help!

zgrose commented 6 years ago

When you see { } it is a dictionary. When you see [ ] it is a collection. Everything else is a value. [ { "foo": "bar", "abc": [1,2,3], "xyzzy":[ "one", "two", "three" ] "test": null }, { "foo": "bar", "abc": [1,2,3], "xyzzy":[ "one", "two", "three" ] "test": null } ] Is a Collection of Dictionary that contains properties as well as Collections of numbers and strings

Given the cURL example, it looks to me like you want to create a single Dictionary of all your values, and then POST that as the body instead of using parameters. Your code will do

make=The Make&model=The Model&etc....

which is how you POST a FORM and that's not what they want.

You want to set your Request.Body to be the JSON object you'll constructing and pass that as a whole thing.

Ma-Trim commented 6 years ago

ohhhhhh well that makes more sense now haha. I will try to do that.

Ma-Trim commented 6 years ago

Okay- here is my new and improved code, but it's not debugging... I am not sure what I am doing wrong. I get "argument not optional" on this line "Listing.Add Condition".

` Dim Client As New WebClient Client.BaseUrl = "https://api.reverb.com/api/" Dim Request As New WebRequest Request.Resource = "listings" Request.Method = WebMethod.Httppost Request.Format = WebFormat.Json Request.AddHeader "Authorization", m_sToken Request.AddHeader "Content-Type", "application/hal+json" Request.AddHeader "Accept", "application/hal+json" Request.AddHeader "Accept-Version", "3.0" Dim Listing As New Dictionary Set Listing = New Dictionary

Listing("make") = sMake
Listing("model") = sModel
Listing("publish") = False

Dim Categories As New Collection
Set Categories = New Collection

Dim Category As Dictionary
Set Category = New Dictionary
Category("uuid") = sCategory

Categories.Add Category

Listing("sold_as_is") = sSoldAsIs
'photos- skip and use web UI to upload

Dim Condition As Dictionary
Set Condition = New Dictionary
Condition("uuid") = sCondition

Listing.Add Condition

Listing("hasinventory") = sHasInventory
Listing("inventory") = dInventoryQTY
Listing("description") = sDescription
Listing("finish") = vFinish
Listing("title") = stitie
Listing("year") = vYear
Listing("sku") = sSKU
Listing("upc") = vUPC
Listing("upc_does_not_apply") = sUPCDoesNotApply
Listing("offers_enabled") = sOffersEnabled
Listing("shipping_profile_id") = vShippingProfileID
Listing("shipping_frofile_name") = vShippingProfileName
'location - skip and default to store address
Listing("origin_country_code") = vOriginCountry

Dim shippingObj As Dictionary
Set shippingObj = New Dictionary

shippingObj("local") = True

Dim rates As Collection
Set rates = New Collection

Dim aRate As Dictionary
Dim anAmount As Dictionary

Set anAmount = New Dictionary
anAmount("amount") = dShippingRateAmountUS
anAmount("currency") = sShippingRateCurrencyUS

Set aRate = New Dictionary
Set aRate("rate") = anAmount
aRate("region_code") = sShippingRateRegionUS

rates.Add aRate

Set anAmount = New Dictionary
anAmount("amount") = dShippingRateAmountNotUS
anAmount("currency") = sShippingRateCurrencyNotUS

Set aRate = New Dictionary
Set aRate("rate") = anAmount
aRate("region_code") = sShippingRateRegionNotUS
rates.Add aRate

Set shippingObj("rates") = rates

Listing.Add("shipping") = shippingObj

Dim Seller As Dictionary
Set Seller = New Dictionary
Seller("paypal_email") = m_sPayPalEmail

Listing.Add Seller

'sellercost - skipping
Listing("tax_exempt") = m_sTaxExempt

Dim Price As Dictionary
Set Price = New Dictionary
Price("amount") = dPrice
Price("currency") = m_sCurrency

Listing.Add Price

'video - skip, use web UI for uploading
Listing("exclusive_channel") = sSellerSite

Debug.Print ConvertToJson(Listing)`

I am working with this JSON:

`{

"make":"string",
"model":"string",
"publish":true,
"categories":[
    {
        "uuid":"string"
    }
],
"sold_as_is":true,
"photos":[
    "string"
],
"condition":{
    "uuid":"string"
},
"has_inventory":true,
"inventory":0,
"storage_location":"string",
"description":"string",
"finish":"string",
"title":"string",
"year":"string",
"sku":"string",
"upc":"string",
"upc_does_not_apply":"string",
"offers_enabled":true,
"shipping_profile_id":"string",
"shipping_profile_name":"string",
"location":{
    "locality":"string",
    "region":"string",
    "country_code":"string"
},
"origin_country_code":"string",
"shipping":{
    "local":true,
    "rates":[
        {
            "region_code":"string",
            "rate":{
                "amount":"string",
                "currency":"string"
            }
        }
    ]
},
"seller":{
    "paypal_email":"string"
},
"seller_cost":"string",
"tax_exempt":true,
"price":{
    "amount":"string",
    "currency":"string"
},
"videos":[
    {
        "link":"string"
    }
],
"exclusive_channel":"seller_site"

}`

zgrose commented 6 years ago

Set Listing("condition") = Condition

You only Add to collections. You need the Key for a Dictionary.

Ma-Trim commented 6 years ago

oh- ok

Ma-Trim commented 6 years ago

Well- I debugged to here! Yay! I have installed VBA-JSON, is there something else I need to do to get this working? @zgrose , Thanks for your help!!

image

zgrose commented 6 years ago

You have more than one Function/Sub called ConvertToJson. Do a Find in your modules and it should be apparent what the issue is.

Ma-Trim commented 6 years ago

well, actually I don't and that was the confusing part.

zgrose commented 6 years ago

The compiler don't lie. :) Maybe you imported the module twice or something? Or you have an object named ConvertToJson. It's gotta be something.

Ma-Trim commented 6 years ago

well, this is the only other one :) in "web helpers" Do i need to set some kind of library or something? Should I reinstall the modules? image

zgrose commented 6 years ago

I don't have your source code so I really can't say other than the compiler is rarely wrong about something.

Ma-Trim commented 6 years ago

Well, I installed the VBA-Web tool, and I don't have any object or another sub named "ConvertToJson" image

I do agree the compiler is rarely wrong :/ It compiles if i comment the debug.print line :)

zgrose commented 6 years ago

Again, I can't do anything with screenshots. You can try googling why the compiler would give you this message.

timhall commented 6 years ago

Hi @Ma-Trim if you're not able to find the duplicate variable, function, sub, etc. you can namespace the call with:

Debug.Print JsonConverter.ConvertToJson(Listing)

It looks like you're doing a great job with nested arrays/values, so I'm going to close this.

@zgrose Thanks for working through it with them!

Ma-Trim commented 6 years ago

Hello- this made it happy, but I will change it to your suggestion - Debug.Print WebHelpers.ConvertToJson(Listing)

THANK YOU @zgrose and @timhall I would be lost without you! :)

Ma-Trim commented 6 years ago

Hello, I am getting the error "argument not optional" on this line: image

zgrose commented 6 years ago

Pretty sure you always need to Set when you're putting or getting an object in VB(A).

Set Dictionary(key) = ObjectVariable Set ObjectVariable = Dictionary(key)

Ma-Trim commented 6 years ago

Ok, I got it :)

Ma-Trim commented 6 years ago

Ok- getting closer! For some reason, my JSON is missing the "category" part.

image

My code-

image

The sample I am trying to reproduce-

image

zgrose commented 6 years ago

I see you adding the uuid to a category dictionary. I see you adding that dictionary to the categories collection. Do you have Set Listing("categories") = Categories anywhere?

Ma-Trim commented 6 years ago

no! That fixed it... Thanks!!!

Ma-Trim commented 6 years ago

Okay- update, I have been trying lots of variations and going in circles, I am really not sure how to get this in the proper order. I have been following the general concept of "{" = dictionary and "[" = collection, but clearly I don't yet understand what to do!

My Json produces this - (just focusing on the shipping object here)

image

And I am trying to do this: (with the exception that I need one for US and one for Non US) image

current code- ` Dim ShippingObj As Dictionary Set ShippingObj = New Dictionary

    ShippingObj("local") = True

    Dim rates As Collection
    Set rates = New Collection

    Dim RatesDetail As Dictionary
    Set RatesDetail = New Dictionary

    RatesDetail("region_code") = vShippingRateRegionUS
    Set RatesDetail("region_code") = rates

    rates.Add RatesDetail

    Dim aRate As Dictionary
    Set aRate = New Dictionary

    aRate("amount") = dShippingRateAmountUS
    aRate("currency") = sShippingRateCurrencyUS

    Set ShippingObj("rate") = aRate

    rates.Add aRate

    Set Listing("shipping") = ShippingObj

`

Ma-Trim commented 6 years ago

Thank you for your help! @zgrose, I am still not able to quite understand how to get the proper JSON structure. See above current code and samples :)

timhall commented 6 years ago

Your above code should read:

Dim Listing As New Dictionary
Dim Shipping As New Dictionary
Dim Rates As New Collection
Dim RateDetails As New Dictionary
Dim Rate As New Dictionary

Rate("amount") = "..."
Rate("currency") = "..."

' Rate: { amount: "...", current: "..." }

RateDetails("region_code") = "..."
Set RateDetails("rate") = Rate

' RateDetails: { region_code: "...", rate: { amount: "...", current: "..." } }

Rates.Add RateDetails

' Rates: [{ region_code: "...", rate: ... }]

Shipping("local") = True
Set Shipping("rates") = Rates

' Shipping: { local: true, rates: [{ region_code: "...", rate: ... }] }

Set Listing("shipping") = Shipping

' Listing: { shipping: { local: true, rates: ... } }

Please address any further general usage questions to Stack Overflow or Code Review as there should be more people that can help there and this isn't really specific to VBA-Web.

Ma-Trim commented 6 years ago

Thank you very much!

Sent from my iPhone Charleen Trimmer Trimmer agency 541-840-6846

On Apr 10, 2018, at 10:56 AM, Tim Hall notifications@github.com wrote:

Your above code should read:

Dim Listing As New Dictionary Dim Shipping As New Dictionary Dim Rates As New Collection Dim RateDetails As New Dictionary Dim Rate As New Dictionary

Rate("amount") = "..." Rate("currency") = "..."

' Rate: { amount: "...", current: "..." }

RateDetails("region_code") = "..." Set RateDetails("rate") = Rate

' RateDetails: { region_code: "...", rate: { amount: "...", current: "..." } }

Rates.Add RateDetails

' Rates: [{ region_code: "...", rate: ... }]

Shipping("local") = True Set Shipping("rates") = Rates

' Shipping: { local: true, rates: [{ region_code: "...", rate: ... }] }

Set Listing("shipping") = Shipping

' Listing: { shipping: { local: true, rates: ... } } Please address any further general usage questions to Stack Overflow or Code Review as there should be more people that can help there and this isn't really specific to VBA-Web.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.