Non-unique key values collection

Dear co-coders,

I am fairly new to coding with Alan and would love some help while my Whatsapp API connector.

To send a whatsapp message, a post request is executed by sending the server a JSON file (figure 5). For the JSON file to be formatted correctly, I created the Interface (figure 4) and implementation (figure 3) accordingly.

The Whatsapp API allows multiple parameters with the exact same key value in its collection. For example:

“parameters”: [
{
“type”: “text”,
“text”: “First text”
},
{
“type”: “text”,
“text”: “Second text”
}
]
However, Alan does not allow me to have the exact same keys in a collection because keys have to be unique such that we can reference them unambiguously. Can anybody help me with this issue? (Note that “type” has to be the key value of the collection in order to format the JSON file correctly.)

  1. Application

This migration will give an error due to the lack of uniqueness of the key values: ‘Type’ is “text” for both.

  1. Migration
  1. Implementation
  1. Interface
  1. Whatsapp API JSON example

Hi Martinique,

The application model, interface implementation and interface describe a provided alan interface. Another alan application can connect to it by describing a consuming interface mapping and adding the interface to its application model. The messages over that interface connection consist of subscriptions, initializations and notifications (when updates occur). The data format used can be json or a specific binary serialization format. So such an interface is not meant to be consumed by non-alan systems.

If you want to connect to non-alan systems, you need what we call a connector. A connector is able to serialize the interface data you describe to the right json format (or parse it, when that is needed). Like this, for instance:

consumer ( )

routine 'test' on
do {
	let $'data' as list headless {
		'a': optional text
		'b': optional text
	} = {
		create (
			'a' = "hello"
		)
		create (
			'b' = "bye"
		)
	}

	switch $'data' => serialize as JSON => is ( "[[\"hello\",null],[null,\"bye\"]]" ) (
		| true => no-op // Test successful
		| false => throw "produced wrong value"
	)
}

You can find more of these examples here: connector/processor grammar - Alan Platform - Model-Driven Software Development Platform

Note that you can map the interface data the desired json output by doing a

=> walk $ . 'parameters' as $ {
	create (
		'type' = $ . 'Type'
		'index' = $ . 'Index'
	)
}

In the alan interface and implementation, you should use a unique value as key, so index seems like the more logical choice instead of type.

Does this help you in the right direction?

Regards, Rick

1 Like

Thank you Rick! Your tips made me rethink my approach and helped a lot. I noticed I made the mistake to try to create the JSON format in the implementation.alan file. However, I realise now that it is formatted easily in the processor.alan file. I would love to share my creation as an example below. If you have any recommendations to improve the efficiency or reliability of my processor, any other tips are welcome. :slightly_smiling_face:

consumer ( )

routine 'Send Message' on collection 'Items'
do walk $ as $ => try {
	let $'Item' = $
	let $'data' as {
		'messaging_product': text
		'recipient_type': text
		'to': text
		'type': text
		'template': {
			'name': text
			'language': {
				'code': text
			}
			'components': list {
				'type': text
				'parameters': list {
					'type': text
					'text': text
				}
			}
		}
	} = (
		'messaging_product' = "whatsapp"
		'recipient_type' = "individual"
		'to' = $'Item'.'Number'
		'type' = $'Item'.'messaging type'
		'template' = (
			'name' = $'Item'.'template name'
			'language' = (
				'code' = $'Item'.'language code'
			)
			'components' = walk $'Item'.'components' as $ => {
				create (
					'type' = $ .'type'
					'parameters' = walk $ .'parameters' as $ => {
						create (
							'type' = $ .'type'
							'text' = $ .'text'
						)
					}
				)
			}
		)
	)
	let $'JSON' = $'data' => serialize as JSON
	let $'response' = true => call 'network'::'http' with (
		$'server'         = "https://graph.facebook.com"
		$'path'           = list ( "/v20.0/", var 'Phone number ID', "/messages" ) => join ( )
		$'authentication' = unset
		$'request'        = new (
			'method' = option 'post'
			'headers' = {
				create [ "Authorization" ] var 'api key'
				create [ "Content-Type" ] "application/json"
			}
			'content' = $'JSON' => call 'unicode'::'as binary' with ( )
		)
	) || throw "HTTP request to WhatsApp API failed"

	// Check response status and set the appropriate Status
	switch $'response'.'status' => less-than ( 400 ) (
		| true => execute $'Item' command 'Set Status' with (
			'Status' = create 'Sent' ( )
		)
		| false => execute $'Item' command 'Set Status' with (
			'Status' = create 'Failed' (
				'Reason' = list ( "Response content: ", $'response'.'content' => call 'unicode'::'import' with ( $'encoding' = "utf-8" ) || throw "Unable to decode response.", ", JSON value: ", $'JSON' ) => join ( )
			)
		)
	)
}
1 Like

Thanks for sharing your solution here!

1 Like

Thanks, at first glance this looks just fine. I later on spotted that I mistakenly lead you to use a headless list, but you luckily found out for yourself already that that was a mistake - nice work!

1 Like

Same question, but reversed this time :slightly_smiling_face:.

A request yields in receiving the following content structure. I only need the text in ‘body’. How can I retrieve this ‘body’ as plain text?

			let $'data' = ^ $'request'.'content' get => call 'unicode'::'import' with ( $'encoding' = "UTF-8" ) => parse as JSON => decorate as {
				'object': text
				'entry': list {
					'changes': list {
						'value': {
							'messages': optional list {
								'text': {
									'body': text
								}
							}
						}
					}
				}
			} || throw "unable to parse request data"

So your data has this structure?

{
    "object": "",
    "entry": [
        {
            "changes": [
                {
                    "value": {
                        "messages": [
                            {
                                "text": {
                                    "body": "this is a test"
                                }
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

And you are only interested in the first items from ‘entry’, ‘changes’ and ‘messages’? What should happen if there are more items in these lists?

Incoming whatsapp messages have the following JSON content format. I wish to retrieve “body” as a variable to use it in the application.alan. Besides, I wish to include the “wa_id” to know who sent the message as well.

Incoming webhook message: {
  "object": "whatsapp_business_account",
  "entry": [
    {
      "id": "987654321",
      "changes": [
        {
          "value": {
            "messaging_product": "whatsapp",
            "metadata": {
              "display_phone_number": "12345678",
              "phone_number_id": "12345678"
            },
            "contacts": [
              {
                "profile": {
                  "name": "Martinique"
                },
                "wa_id": "0612345678"
              }
            ],
            "messages": [
              {
                "from": "0612345678",
                "id": "wamid.=",
                "timestamp": "1733219661",
                "text": {
                  "body": "Hello world"
                },
                "type": "text"
              }
            ]
          },
          "field": "messages"
        }
      ]
    }
  ]
}

You can access list items by their index and you should retrieve optional items with a get operator.

If you combine these two methods, you can get to body with this expression:

let $'body' = $'data'.'entry'[ 0 ] .'changes'[ 0 ] .'value'.'messages' get [ 0 ] .'text'.'body' || throw "no body text found"

You should be able to do something similar for the wa_id.

1 Like