Get the first element of a headless JSON array in the connector

I want to map the first value of a JSON-array to a text property if there is one available.

I use the openlibrary API. In the result there is a list of covers that are available. I want to use the first one, if there is at least one item available.

Requesting this: https://openlibrary.org/isbn/9789047508809.json will result in:

{
	"publishers": [
	  "UNIEBOEK | HET SPECTRUM"
	],
	"source_records": [
	  "amazon:9047508807"
	],
	"title": "Dummie de mummie en de gouden scarabee (Dutch Edition)",
	"number_of_pages": 251,
	"last_modified": {
	  "type": "/type/datetime",
	  "value": "2019-07-09T18:36:29.101167"
	},
	"covers": [
	  8738115
	],
	"created": {
	  "type": "/type/datetime",
	  "value": "2019-07-09T18:36:29.101167"
	},
	"isbn_13": [
	  "9789047508809"
	],
	"isbn_10": [
	  "9047508807"
	],
	"publish_date": "Jul 01, 2009",
	"key": "/books/OL27098618M",
	"authors": [
	  {
		"key": "/authors/OL7565384A"
	  }
	],
	"latest_revision": 1,
	"works": [
	  {
		"key": "/works/OL19914026W"
	  }
	],
	"type": {
	  "key": "/type/edition"
	},
	"revision": 1
  }

Hoe should I map a list of elements to a text property under a state?

I’ve red the grammar, but could not find a construct to solve my problem

The following processor does not work/compile:

provider ( )

init main

routine 'Verzoek' on command 'Verzoek ISBN Informatie'
	$ 'ISBN Info' =
do {
	let $'res' = true => call 'network'::'http' with (
		$'server' = "https://openlibrary.org"
		$'path' = list ( "/isbn/" , $ . 'ISBN', ".json" ) => join ( )
		$'authentication' = unset
		$'request' = new (
			'method' = option 'get'
		)
	) || throw "Verzoek om ISBN informatie is mislukt."

	@log: list ( "Informatie verzocht voor ISBN " , $ .'ISBN' ) => join ( )

	switch $ 'res' .'status' => is ( 302 ) (
		| true => {
			let $ 'location' = ^ $'res'.'headers' ["location"] || throw "no 'location' header"
			let $'res' = true => call 'network'::'http' with (
				$'server' = $ 'location'
				$'path' = ""
				$'authentication' = unset
				$'request' = new (
					'method' = option 'get'
				)
			) || throw "ISBN redirect request failed"

			switch $ 'res' .'status' => less-than ( 400 ) (
				| true => {
					let $ 'ISBN Informatie' = ^ $ 'res' .'content' => call 'unicode'::'import' with ( $'encoding' = "UTF-8" ) => parse as JSON => decorate as {
						'title': text
						'covers': list headless { // is headless the right term?
							'cover'	: text
						}
					} || throw "Snap het resultaatbericht niet"

					execute ^ ^ ^ $'ISBN Info' event 'ISBN Informatie' with (
						'ISBN' = $ . 'ISBN'
						'Gevonden' = create 'Ja' (
							'Naam' = $ 'ISBN Informatie' . 'title'
							'Kaft' = switch $ 'ISBN Informatie' . 'covers' ( //How to access the first cover?
								| value as $ => create 'Onbeschikbaar' ( )
								| error => create 'Onbeschikbaar' ( )
							)
						)
					)
				}
				| false => throw "ISBN redirect request failed"
			)
		}
		| false => switch $ 'res' .'status' => is ( 404 ) (
			| true => execute ^ $'ISBN Info' event 'ISBN Informatie' with (
				'ISBN' = $ . 'ISBN'
				'Gevonden' = create 'Nee' ( )
			)
			| false => throw "ISBN redirect failed"
		)
	)
}

headless here is used incorrectly. It is an instruction that the data does not contain model/schema information to assist in parsing. For JSON this means we match elements in an array based on their index to the index of a property in the node.
Example:

headless {
  'a': text
  'b': text
}

with JSON:

[
  "hello",
  "world"
]

here "hello" will be assigned to 'a' and "world" to 'b'.
However, this requires an identical amount of entries in the array as there are properties in the node.

Another thing to note, JSON is type aware. So the array "covers" in this case, contains numbers and not texts. When the type in the data (number) en the type is the schema (text) do not match, the decorator fails.

Finally, we currently do not provided functionality to reliably access specific entries in a list. Since version 35, all plural values are index and can be accessed with [ <key> ]. For list the key would be an integer, but no function will ever re-index a plural value, so using [ 0 ] to access the ‘first’ entry will return the wrong entry after sorting the list.

Alternatively you can use the standard library function 'plural'::'select', which requires a function as parameter that unambiguously selects a single entry. With less-than/greater-than you can select the smallest/largest nummer in the list.

Ok thanks!

It needed some more digging in the documentation. I’ve eventually come up with the following. Getting the number format correct was also tricky, because the debugger only states that there is a difference in the result, but it does not show the actual result.

provider ( )

init main

routine 'Verzoek' on command 'Verzoek ISBN Informatie'
	$ 'ISBN Info' =
do {
	let $'res' = true => call 'network'::'http' with (
		$'server' = "https://openlibrary.org"
		$'path' = list ( "/isbn/" , $ . 'ISBN', ".json" ) => join ( )
		$'authentication' = unset
		$'request' = new (
			'method' = option 'get'
		)
	) || throw "Verzoek om ISBN informatie is mislukt."

	@log: list ( "Informatie verzocht voor ISBN " , $ .'ISBN' ) => join ( )

	switch $ 'res' .'status' => is ( 302 ) (
		| true => {
			let $ 'location' = ^ $'res'.'headers' ["location"] || throw "no 'location' header"
			let $'res' = true => call 'network'::'http' with (
				$'server' = $ 'location'
				$'path' = ""
				$'authentication' = unset
				$'request' = new (
					'method' = option 'get'
				)
			) || throw "ISBN redirect request failed"

			switch $ 'res' .'status' => less-than ( 400 ) (
				| true => {
					let $ 'ISBN Informatie' = ^ $ 'res' .'content' => call 'unicode'::'import' with ( $'encoding' = "UTF-8" ) => parse as JSON => decorate as {
						'title': text
						'covers': list integer
					} || throw "Snap het resultaatbericht niet"

					execute ^ ^ ^ $'ISBN Info' event 'ISBN Informatie' with (
						'ISBN' = $ . 'ISBN'
						'Gevonden' = create 'Ja' (
							'Naam' = $ 'ISBN Informatie' . 'title'
							'Kaft' = switch $ 'ISBN Informatie' . 'covers' [ 0 ] (
								| value as $ => create 'Beschikbaar' (
									'Kaft' = {
										let $'data' as 'unicode'/'message data' = (
											'types' = {
												create ["cover"] create 'number' $
											}
										)
										"https://covers.openlibrary.org/b/id/{cover,number,:: group-off}-M.jpg" => call 'unicode'::'format' with (
											$'data' = $'data'
											$'locale' = "nl_NL"
										) || throw "Kan de cover URL niet opmaken"
									}
								)
								| error => create 'Onbeschikbaar' ( )
							)
						)
					)
				}
				| false => throw "ISBN redirect request failed"
			)
		}
		| false => switch $ 'res' .'status' => is ( 404 ) (
			| true => execute ^ $'ISBN Info' event 'ISBN Informatie' with (
				'ISBN' = $ . 'ISBN'
				'Gevonden' = create 'Nee' ( )
			)
			| false => throw "ISBN redirect failed"
		)
	)
}