Search code examples
springthymeleaftemplate-engine

How to use Thymeleaf to process YAML files?


I'm trying to process YAML files using Thymeleaf. A sample file is given below:

apiVersion: v1
kind: Service
metadata:
  name: [[${app['name']}]]
  labels:
    app: [[${app['name']}]]
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    name: http
  - port: 443
    protocol: TCP
    name: https
  selector:
    app: nginx

The value app.name comes from another YAML file that I parse at runtime.

What I've tried so far:

  1. Creating a MessageSource with Properties

    TemplateEngine templateEngine = new TemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    StandardMessageResolver messageResolver = new StandardMessageResolver();
    messageResolver.setDefaultMessages(props); // contains app.name
    templateEngine.setMessageResolver(messageResolver);
    
  2. Setting a variable in the context

    map.put("app.name", "test");
    context.setVariables(map); // contains app.name
    

But I keep getting the error:

Exception evaluating OGNL expression: "app['name']
...
Caused by: ognl.OgnlException: source is null for getProperty(null, "name")

Using Thymeleaf 3.0.3.RELEASE. I'm using Spring and not using Spring, in the sense that the spring-boot-starter-thymeleaf brings in so much baggage mostly needed for HTML, I decided to just instantiate the template resolver and engine myself. It seems like people rarely process TEXT using Thymeleaf; all the examples I came across are HTML.

I'd also like to know how to include a fragment of YAML in my templates.

Edit: Thanks to @Metroids, I got it working. Here's the link to my sample app in case someone else has a similar problem.


Solution

  • I think there is some confusion about how to access properties here... if you want to use the expression ${app['name']} in your template, your context should look like this:

    Map<String, Object> app = new HashMap<>();
    app.put("name", "test");
    
    Context context = new Context();
    context.setVariable("app", app);
    engine.process("template", context);
    

    Using map.put("app.name", "test"); is not a good idea because a syntax of something.something has a meaning in expression language (calling a getter/setter on an object).

    Edit: Including a text template would look something like this:

    a.txt

    blah blah blah 
    [# th:insert="b"/] 
    blah blah blah 
    

    b.txt

    Text in b.txt
    that should be included
    

    Edit 2: If you want to use messages, rather than a context this should work:

    apiVersion: v1
    kind: Service
    metadata:
      name: [[#{app.name}]]
      labels:
        app: [[#{app.name}]]