Skip to main content
Skip table of contents

Writing your own PocketQuery Converters

PocketQuery Converters are functions written in JavaScript. They help you convert the results obtained from your Datasource into a different (usually simpler) format. One of the most typical use cases of Converters is value unwrapping, adding information, or performing some calculations with the obtained data.

In this tutorial, you will learn the following:

PocketQuery Converters

Converters play an important role in PocketQuery Query execution. Whenever you execute a Query, the raw result from your Datasource is passed to the Query’s Converter function as a parameter (see more The PocketQuery Pipeline). The main task of the Converter is to transform this raw result into something more convenient; i.e., prepare it for rendering. The result of this function is then passed to the Query Template and is accessible via the $result variable.

Not familiar with PocketQuery Templates? Follow our step-by step tutorial to improve on the user interface of your query data: Writing your own PocketQuery Templates!

In the following section, you can find some basic examples with explanations.

Getting started: Why do we need Converters?

We have Queries that obtain results from Datasources and we render these results using Templates. But why do we need Converters? Let’s first look at an example of a most basic Converter:

JS
function convert(json) {
  const parsed = JSON.parse(json);
  
  return parsed;
}

This code is also considered the default Converter, which is used if you don’t specify a different one (more about default Converters here: Converters).

Explanation: in every Converter, you have to implement a convert(json) function. This function is called after every Query execution. For the json parameter, you can expect the raw result returned from the Datasource. This parameter is always of type string. To enable further processing, you need to convert it to a structured data type (JavaScript object). You can do so by calling the JSON.parse(...) function. Thanks to this conversion, you can now access object attributes using dot notation (.). For example, let’s assume the following Query result:

TEXT
'[{"id": "city_23", "name":"Prague"}, {"id": "city_24", "name": "Brno"}]'

Calling JSON.parse converts it to the following object:

JSON
[
  {
    id: "city_23",
    name: "Prague"
  },
  {
    id: "city_24",
    name: "Brno"
  }
]

This object is then returned as a result of the function. Thanks to this conversion, we can now use the following Template:

HTML
#foreach ($city in $result)
  <p>$city.id: $city.name</p>
#end

This will print all objects in our JSON array. Without the conversion, the $result would be a simple string without any structure and we would not be able to access its attributes.

Adding attributes to Query result

You can use a Converter to add additional information to the $result object. Let’s consider our JSON array containing cities from the previous section. Each city has 2 attributes: id and name. We might want to also display country. Unfortunately, this information is not returned from the data source. However, we can append this information manually:

JS
function convert(json) {
  const parsed = JSON.parse(json);

  return parsed.map(city => {
    city.country = 'Czechia';
    return city;
  });
}

Explanation: we used .map(object => {...}) construction to transform each object to something different. In our case, we added an extra attribute with key country and value “Czechia”. If you are not familiar with the Lambda notation we used, you can change it to a loop to make it easier to read:

JS
function convert(json) {
  const parsed = JSON.parse(json);

  for (let i = 0; i < parsed.length; i++) {
    parsed[i].country = 'Czechia';
  }
  
  return parsed;
}

Both these examples are semantically equal. Now, we can print the new attribute in our Template, too:

HTML
#foreach ($city in $result)
  <p>$city.id: $city.name, COUNTRY: $city.country</p>
#end

In a real use case scenario, you don’t want to add a static value but calculate the new attribute from other attributes. Let’s say we want to convert “name” to a Wikipedia link. We can do that using the following Converter:

JS
function convert(json) {
  const parsed = JSON.parse(json);

  return parsed.map(city => {
    city.url = 'https://en.wikipedia.org/wiki/' + city.name;
    return city;
  });
}

Or alternatively:

JS
function convert(json) {
  const parsed = JSON.parse(json);

  for (let i = 0; i < parsed.length; i++) {
    parsed[i].url = 'https://en.wikipedia.org/wiki/' + parsed[i].name;
  }
  
  return parsed;
}

In this example, we added an attribute called url, which was constructed using the name attribute. Result of the execution is then the following:

JSON
[
  {
    id: "city_23",
    name: "Prague",
    url: "https://en.wikipedia.org/wiki/Prague"
  },
  {
    id: "city_24",
    name: "Brno",
    url: "https://en.wikipedia.org/wiki/Brno"
  }
]

The same function can be used for overriding an existing attribute. Simply use city.name = ... to change the city name.

Converter vs. Template: What should (not) go into a Converter?

Converters are designed to have a sole purpose: preparing data for rendering. However, the difference in responsibility between Converters and Templates might be unclear at times. For example, in the previous section, we added the url parameter to our data source result. A Template displaying this parameter can look like the following:

HTML
#foreach ($city in $result)
  <a href="$city.url">$city.name Wikipedia link</a>
#end

However, we can achieve the same effect by using the most simple Template (introduced at the beginning of this tutorial) and move the logic to the Template:

HTML
#foreach ($city in $result)
  <a href="https://en.wikipedia.org/wiki/$city.name">$city.name Wikipedia link</a>
#end

Or we can go the opposite direction and move everything to our Converter by setting city.url to:

JS
city.url = '<a href="https://en.wikipedia.org/wiki/"' + city.name + '>' + city.name + 'Wikipedia link</a>'

Which would simplify our Template to:

HTML
#foreach ($city in $result)
  $city.url
#end

So how do we determine whether we should put our logic into a Converter or a Template? Despite there are cases, where both ways are considered ‘clean’, in general, you should stick to the following conventions:

  • Templates are used only to render results. There should not be any value conversion or complex logic.

  • All calculations should be in the Converter (if possible).

  • All HTML code should be in the Template, the Converter should contain only ‘raw’ values.

Advanced conversions: arbitrarily structured Query results

So far, we focused only on very basic scenarios. However, in the real world, a Query might return some arbitrarily structured data, which is difficult to process. Typically, a returned result is not an array but an object (wrapping the actual array of results):

CODE
{
  results: [
    {
      id: "city_23",
      name: "Prague"
    },
    {
      id: "city_24",
      name: "Brno"
    },
    ...
  ]
} 

Attributes might be structured, too:

CODE
{
  queryResults: [
    {
      id: "city_23",
      info : {
        name: "Prague",
        priceCzk: 10.0
      }
    },
    ...
  ]
} 

In the following example, we want to:

  1. Convert the name parameter to upper case.

  2. Convert the currency (Czech Koruna) in priceCzk to Euro and store it in a new parameter priceEur(let’s consider rate 1 EUR = 26.6 CZK).

  3. Remove other attributes.

A Converter corresponding to our requirements may look as follows:

JS
function convert(json) {
  const parsed = JSON.parse(json);

  return parsed.queryResults.map(city => {
    return {
      name: city.info.name.toUpperCase(),
      priceEur: city.info.priceCzk * 26.6
    };
  });
}

Explanation: on line 4, we unwrap the JSON object using .queryResults notation and iterate only over the inner array containing the actual results. We map each of these results to a new (initially empty) object. In each of these mapping iterations, we do the following:

  1. Create a new empty object (line 5)

  2. Create a new attribute called name and assign it a value corresponding corresponding to an upper case version of city.info.name (line 6).

  3. Create a priceEur attribute and assign it a value equal to amount of city.info.priceCzk in EUR (line 7).

  4. Return the new object, which will override the previous value (line 8).

Let’s consider the following Query result:

JSON
{
  queryResults: [
    {
      id: "city_23",
      info : {
        name: "Prague",
        priceCzk: 10.0
      }
    },
    {
      id: "city_24",
      info : {
        name: "Brno",
        priceCzk: 0.0
      }
    }
  ]
} 

It will be transformed to:

JSON
[
  {
    name: "PRAGUE",
    priceEur: 266.0
  },
  {
    name: "BRNO",
    priceEur: 0.0
  }
]

When it does not work as expected

Sometimes the result returned from the Converter is not corresponding to the expected one. Or your Converter code might not compile at all. For these cases, there is a debug mode option, which can help you with troubleshooting.

You can find the raw result (returned from your data source) as the Raw Query Result:

This JSON array is exactly the (string) representation passed to your Converter. Similarly, you can see the result returned from your Converter in the Raw Converter Result section:

If you’re curious, we acquired this behavior using the following code:

JSON
function convert(json) {
  const parsed = JSON.parse(json);

  return parsed.map(splitIdToParts);
}

function splitIdToParts(elem) {
  return {
    idPart1: elem.id.substr(0, 13),
    idPart2: elem.id.substr(14)
  };
}

However, sometimes our code does not run at all. For example, if we forget a parenthesis in our code:

In these cases, PocketQuery tries to provide a description of where the problem lies and what went wrong. All you need to do is to execute your Query:

Similarly, you can see an error message in the debug mode:

Build your own Converter

In this guide, we provided a brief introduction into vanilla JavaScript and PocketQuery Converters. Now, you can start building own converters to transform your query result the way you need. Having questions? Issues? Feedback? Don’t hesitate to contact us!

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.