Skip to main content
Solved

Webhook request body extended hash to string

  • January 20, 2026
  • 6 replies
  • 161 views

Theo Hollenberg
Forum|alt.badge.img+2

I want to achieve the following:

I have a webhook called wh-ar-00 which should receive a body with content-type ‘application/json’.

I want to pass this body to another webhook wh-msg-00 which expects the following format:

{
  "source”: "WH-AR-00”,
  "body”: ${_request.body}
}

As the _request.body is an extended hash, I need to transform this to a JSON string to pass it on. How do I do this with an extended hash?

Best answer by Jeppe Gents

Hey Hollenberg.

I created this Freemarker Macro which does exactly what you want. 😊

convertToJson Input any object and print it to a JSON string.

<#macro convertToJson myHash level=0 escape=false>
<#local _spacer = " ">
<#if (myHash?is_hash)!false>{<#if (myHash?is_hash_ex)!false><#list myHash as key, value>
${""?left_pad((level+1), _spacer)}"${key}": <#if (value?is_string)!false><#if escape!false>"${value?json_string}"<#else>"${value}"</#if><#elseif (value?is_number)!false>${value}<#elseif (value?is_boolean)!false>${value?boolean}<#elseif (value?is_hash)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?is_sequence)!false><#if (value?has_content)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?size == 0)!false>[]<#else>"macroError: Sequence value/content could not be determined or tested."</#if><#elseif (value?is_method)!false>"THIS is a METHOD"<#elseif (value?is_directive)!false>"THIS is a Directive"<#elseif (!value?has_content)!false>${value?cn}<#else>"macroError: Value type could not be determined."</#if><#sep>,</#list>
${""?left_pad(level, _spacer)}</#if>}<#elseif (myHash?is_sequence)!false>[<#list myHash as _array><#if (_array?is_hash)!false>
${""?left_pad((level+1), _spacer)}<@convertToJson myHash=_array level=(level+1) escape=escape/><#if _array?is_last>
${""?left_pad(level, _spacer)}</#if><#elseif (_array?is_string)!false><#if escape!false>"${_array?json_string}"<#else>"${_array}"</#if><#elseif (_array?is_number)!false>${_array}<#elseif (_array?is_boolean)!false>${_array?boolean}<#elseif (!_array?has_content)!false>${_array?cn}</#if><#sep>,</#list>]</#if><#return></#macro>

 

In your case I would assign a variable, lets name it “myJsonText”, and give it the following value.

<#macro convertToJson myHash level=0 escape=false>
<#local _spacer = " ">
<#if (myHash?is_hash)!false>{<#if (myHash?is_hash_ex)!false><#list myHash as key, value>
${""?left_pad((level+1), _spacer)}"${key}": <#if (value?is_string)!false><#if escape!false>"${value?json_string}"<#else>"${value}"</#if><#elseif (value?is_number)!false>${value}<#elseif (value?is_boolean)!false>${value?boolean}<#elseif (value?is_hash)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?is_sequence)!false><#if (value?has_content)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?size == 0)!false>[]<#else>"macroError: Sequence value/content could not be determined or tested."</#if><#elseif (value?is_method)!false>"THIS is a METHOD"<#elseif (value?is_directive)!false>"THIS is a Directive"<#elseif (!value?has_content)!false>${value?cn}<#else>"macroError: Value type could not be determined."</#if><#sep>,</#list>
${""?left_pad(level, _spacer)}</#if>}<#elseif (myHash?is_sequence)!false>[<#list myHash as _array><#if (_array?is_hash)!false>
${""?left_pad((level+1), _spacer)}<@convertToJson myHash=_array level=(level+1) escape=escape/><#if _array?is_last>
${""?left_pad(level, _spacer)}</#if><#elseif (_array?is_string)!false><#if escape!false>"${_array?json_string}"<#else>"${_array}"</#if><#elseif (_array?is_number)!false>${_array}<#elseif (_array?is_boolean)!false>${_array?boolean}<#elseif (!_array?has_content)!false>${_array?cn}</#if><#sep>,</#list>]</#if><#return></#macro>
{
"source”: "WH-AR-00”,
"body”: <@convertToJson myHash=_request["body"] level=1 escape=true>
}

 

From there on, you can simply add ${_variables[“myJsonText”]} to insert the whole the json body as a string.

OR

Use ?eval_json Freemarker built-in to have Freemarker treat the string as a JSON object.
Which enables you to do the following.
${(_variables[“myJsonText”]?eval_json)[“source”]}

 

I have used this macro in a lot of projects, since it allows me to print any type of object, to a JSON valid string.

The macros “myHash” input support everything, not just hashes. 😁
So anything Freemarker treats as an object, can be converted into a JSON valid string.

 

Have Fun 👋😋

 

Best Regards.
Jeppe Gents
(Former ‘Solutions & Integration Specialist’ employee at TOPdesk Denmark A/S)

6 replies

Theo Hollenberg
Forum|alt.badge.img+2

@Sanne van Opstal-Brakel , ik voorzie dat dit een lastige is voor de community. Is dit iets voor een PO Action Management om bijvoorbeeld een nieuw java object te exposen aan de door jullie gebruikte Freemarker engine?

Omdat ik begrijp dat tijd altijd een issue is, wil ik best wat voorwerk doen:

Dit zou dan de class zijn:

import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.ext.beans.BeanModel;

import java.util.List;
import java.util.Map;

public class ToJsonMethod implements TemplateMethodModelEx {

private static final ObjectMapper mapper = new ObjectMapper();

@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 1) {
throw new TemplateModelException("toJson expects exactly 1 argument");
}

TemplateModel model = (TemplateModel) arguments.get(0);

Object unwrapped = freemarker.template.utility.DeepUnwrap.unwrap(model);

try {
return mapper.writeValueAsString(unwrapped);
} catch (Exception e) {
throw new TemplateModelException("Failed to transform to JSON", e);
}
}
}

DeepUnwrap.unwrap(...) is cruciaal. Dit zet extended hashes (SimpleHash, DefaultMapAdapter, etc.) om naar echte Java Maps en Lists.

Daardoor werkt dit voor alle FreeMarker hashes, ook nested.


Toevoegen aan Freemarker gaat dan zo:

Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);

cfg.setSharedVariable("toJson", new ToJsonMethod());


En het gebruik zou dan dan zo zijn:

${toJson(data)}

 


Sanne van Opstal-Brakel
Community Manager
Forum|alt.badge.img+8

I am wondering is some one like ​@Rico Roodenburg  has any ideas on this?

In the mean time I will notify the product manager of action management ​@Theo Hollenberg and share this post and your request. I have also created a tip request out of this for you already 😉


Forum|alt.badge.img+6

I want to achieve the following:

I have a webhook called wh-ar-00 which should receive a body with content-type ‘application/json’.

I want to pass this body to another webhook wh-msg-00 which expects the following format:

{
  "source”: "WH-AR-00”,
  "body”: ${_request.body}
}

As the _request.body is an extended hash, I need to transform this to a JSON string to pass it on. How do I do this with an extended hash?

@Sanne van Opstal-Brakel , ik voorzie dat dit een lastige is voor de community. Is dit iets voor een PO Action Management om bijvoorbeeld een nieuw java object te exposen aan de door jullie gebruikte Freemarker engine?

Omdat ik begrijp dat tijd altijd een issue is, wil ik best wat voorwerk doen:

Dit zou dan de class zijn:

import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.ext.beans.BeanModel;

import java.util.List;
import java.util.Map;

public class ToJsonMethod implements TemplateMethodModelEx {

private static final ObjectMapper mapper = new ObjectMapper();

@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 1) {
throw new TemplateModelException("toJson expects exactly 1 argument");
}

TemplateModel model = (TemplateModel) arguments.get(0);

Object unwrapped = freemarker.template.utility.DeepUnwrap.unwrap(model);

try {
return mapper.writeValueAsString(unwrapped);
} catch (Exception e) {
throw new TemplateModelException("Failed to transform to JSON", e);
}
}
}

DeepUnwrap.unwrap(...) is cruciaal. Dit zet extended hashes (SimpleHash, DefaultMapAdapter, etc.) om naar echte Java Maps en Lists.

Daardoor werkt dit voor alle FreeMarker hashes, ook nested.


Toevoegen aan Freemarker gaat dan zo:

Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);

cfg.setSharedVariable("toJson", new ToJsonMethod());


En het gebruik zou dan dan zo zijn:

${toJson(data)}

 

Agree with you. TOPdesk should create a method for this, since this is not possible by freemarker. They already have created some methods like the _base64Decode".


Forum|alt.badge.img
  • Employee
  • January 29, 2026

Hi, I'm one of the developers responsible for Events & Actions. We make the unparsed request body of a Webhook available under  ${_request["rawBody"]}. (This is what’s inserted if you select “Full request body” in the Insert Value popup.)

By default, the checkbox “Escape values inserted into body”  is turned on, and that will escape every value that's inserted into the JSON request body. If you want to preserve the JSON structure of the request body, you'll need to either uncheck that box, or alternatively, request that this one value isn't escaped by adding ?no_esc in front of the closing curly brace. That would look like this:

{
"source”: "WH-AR-00”,
"body”: ${_request["rawBody"]?no_esc}
}

Does this solve your use case?


Theo Hollenberg
Forum|alt.badge.img+2

Hi ​@IrisH,

It does partly, as I also want to store the values of properties from the incoming message which are hashes as well. For instance a body like:

{
"date": "",
"source": "",
"message": {
"id": "xxxxxxx",
"value": {
}
}
}

where I only what to save the message value as a json text. Keep in mind that this is only an example as I cannot elaborate on the structure I'm getting in.

By the way: are all properties of the _request hash documented? If so, where can I find them?


Forum|alt.badge.img+1
  • New Member
  • Answer
  • January 30, 2026

Hey Hollenberg.

I created this Freemarker Macro which does exactly what you want. 😊

convertToJson Input any object and print it to a JSON string.

<#macro convertToJson myHash level=0 escape=false>
<#local _spacer = " ">
<#if (myHash?is_hash)!false>{<#if (myHash?is_hash_ex)!false><#list myHash as key, value>
${""?left_pad((level+1), _spacer)}"${key}": <#if (value?is_string)!false><#if escape!false>"${value?json_string}"<#else>"${value}"</#if><#elseif (value?is_number)!false>${value}<#elseif (value?is_boolean)!false>${value?boolean}<#elseif (value?is_hash)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?is_sequence)!false><#if (value?has_content)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?size == 0)!false>[]<#else>"macroError: Sequence value/content could not be determined or tested."</#if><#elseif (value?is_method)!false>"THIS is a METHOD"<#elseif (value?is_directive)!false>"THIS is a Directive"<#elseif (!value?has_content)!false>${value?cn}<#else>"macroError: Value type could not be determined."</#if><#sep>,</#list>
${""?left_pad(level, _spacer)}</#if>}<#elseif (myHash?is_sequence)!false>[<#list myHash as _array><#if (_array?is_hash)!false>
${""?left_pad((level+1), _spacer)}<@convertToJson myHash=_array level=(level+1) escape=escape/><#if _array?is_last>
${""?left_pad(level, _spacer)}</#if><#elseif (_array?is_string)!false><#if escape!false>"${_array?json_string}"<#else>"${_array}"</#if><#elseif (_array?is_number)!false>${_array}<#elseif (_array?is_boolean)!false>${_array?boolean}<#elseif (!_array?has_content)!false>${_array?cn}</#if><#sep>,</#list>]</#if><#return></#macro>

 

In your case I would assign a variable, lets name it “myJsonText”, and give it the following value.

<#macro convertToJson myHash level=0 escape=false>
<#local _spacer = " ">
<#if (myHash?is_hash)!false>{<#if (myHash?is_hash_ex)!false><#list myHash as key, value>
${""?left_pad((level+1), _spacer)}"${key}": <#if (value?is_string)!false><#if escape!false>"${value?json_string}"<#else>"${value}"</#if><#elseif (value?is_number)!false>${value}<#elseif (value?is_boolean)!false>${value?boolean}<#elseif (value?is_hash)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?is_sequence)!false><#if (value?has_content)!false><@convertToJson myHash=value level=(level+1) escape=escape/><#elseif (value?size == 0)!false>[]<#else>"macroError: Sequence value/content could not be determined or tested."</#if><#elseif (value?is_method)!false>"THIS is a METHOD"<#elseif (value?is_directive)!false>"THIS is a Directive"<#elseif (!value?has_content)!false>${value?cn}<#else>"macroError: Value type could not be determined."</#if><#sep>,</#list>
${""?left_pad(level, _spacer)}</#if>}<#elseif (myHash?is_sequence)!false>[<#list myHash as _array><#if (_array?is_hash)!false>
${""?left_pad((level+1), _spacer)}<@convertToJson myHash=_array level=(level+1) escape=escape/><#if _array?is_last>
${""?left_pad(level, _spacer)}</#if><#elseif (_array?is_string)!false><#if escape!false>"${_array?json_string}"<#else>"${_array}"</#if><#elseif (_array?is_number)!false>${_array}<#elseif (_array?is_boolean)!false>${_array?boolean}<#elseif (!_array?has_content)!false>${_array?cn}</#if><#sep>,</#list>]</#if><#return></#macro>
{
"source”: "WH-AR-00”,
"body”: <@convertToJson myHash=_request["body"] level=1 escape=true>
}

 

From there on, you can simply add ${_variables[“myJsonText”]} to insert the whole the json body as a string.

OR

Use ?eval_json Freemarker built-in to have Freemarker treat the string as a JSON object.
Which enables you to do the following.
${(_variables[“myJsonText”]?eval_json)[“source”]}

 

I have used this macro in a lot of projects, since it allows me to print any type of object, to a JSON valid string.

The macros “myHash” input support everything, not just hashes. 😁
So anything Freemarker treats as an object, can be converted into a JSON valid string.

 

Have Fun 👋😋

 

Best Regards.
Jeppe Gents
(Former ‘Solutions & Integration Specialist’ employee at TOPdesk Denmark A/S)