API Management is a great tool for “fronting” your companies APIs and has some powerful tools to shape and transform those operations. One such tool is the set-body
policy. This policy lets you shape either the request sent to the backend (if placed in the <inbound>
element) or the response sent to the client (if called in the <outbound>
section).
The policy works in one of 2 ways, using code snippets (C# with a subset of the .net framework) or using liquid templates. The code snippets are great for minor modifications (e.g. remove or add a property to the existing body) but liquid templates give you an editor experience that is closer to the expected output.
At a high level, lets assume I want to call the ICanHazDadJoke API. (I am a sucker for terrible joke!) The search api
GET https://icanhazdadjoke.com/search?term=cat
Accept: application/json
will return the following json payload (truncated results)..
{
"current_page": 1,
"limit": 8,
"next_page": 2,
"previous_page": 1,
"results": [
{
"id": "O7haxA5Tfxc",
"joke": "Where do cats write notes?\r\nScratch Paper!"
},
{
"id": "TS0gFlqr4ob",
"joke": "What do you call a group of disorganized cats? A cat-tastrophe."
}
],
"search_term": "cat",
"status": 200,
"total_jokes": 10,
"total_pages": 2
}
I have created a new API Operation in APIM and configured the backend to point at icanhazdadjoke.com.
lets assume instead of returning this object we just want to return a json array of the jokes. We can do this with a set-body
policy in APIM (inside the <outbound>
section).
<set-body template="liquid">{
"results": [
{% JSONArrayFor joke in body.results %}
"{{ joke.joke }}"
{% endJSONArrayFor %}
]
}</set-body>
We are using JSONArrayFor
here instead of for
to ensure no trailing comma is left.
My API call now returns something like this…
{
"results": [
"Where do cats write notes?
Scratch Paper!",
"What do you call a group of disorganized cats? A cat-tastrophe."
]
}
Almost there, but when I try to test this using the excellent rest-client extension in VSCode, it complains that the body isn’t a valid JSON payload! The reason is the rogue \r\n
in the first joke, so we need to strip out/convert any newlines before we return the response. Enter liquid filters!
According to the documentation I should be able to change my set-body
policy to use one or more filters like this (I am using both to really make sure they are gone 😀, actually newline_to_br adds a br element in front of any \r\n but we still need to get rid of them, this way the intended formatting is preserved as html)..
<set-body template="liquid">{
"results": [
{% JSONArrayFor joke in body.results %}
"{{ joke.joke | newline_to_br | strip_newlines }}"
{% endJSONArrayFor %}
]
}</set-body>
Run this through APIM and there is no change to the output! How odd! Some more reveals some odd truths, the implementation of liquid in APIM is based on the dotliquid library (source). Nestled amongst the documentation for dotliquid is a reference to Filter and Output Casing, which states that
“DotLiquid uses this same convention by default, but can also be changed to use C# naming convention, in which case output fields and filters would be referenced like so “
hmmm, I wonder if the C# naming convention is the default for APIM, if so then the following set-body
policy should work..
<set-body template="liquid">{
"results": [
{% JSONArrayFor joke in body.results %}
"{{ joke.joke | NewlineToBr | StripNewlines }}"
{% endJSONArrayFor %}
]
}</set-body>
🎉ta-da🎉 valid json.
{
"results": [
"Where do cats write notes?<br />Scratch Paper!",
"What do you call a group of disorganized cats? A cat-tastrophe."
]
}