준호씨의 블로그

FreeMarker - null 데이터 오류 안나도록 하기. 포멧팅한 date는? 본문

개발이야기

FreeMarker - null 데이터 오류 안나도록 하기. 포멧팅한 date는?

준호씨 2021. 1. 3. 18:41
반응형

freemarker에서 아무 처리를 해 주지 않은 경우 null을 대입해 주면 오류가 발생합니다.

freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> user  [in template "name" at line 1, column 9]

----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
	- Failed at: ${user}  [in template "name" at line 1, column 7]
----

에러 메시지에 친절하게 null처리 방법을 알려줍니다. 

변수명 뒤에 "!"를 붙이고 그 뒤에 기본값을 넣어주거나 if 문을 사용하고 변수명 뒤에 "??"를 붙여 주는 방법입니다. 만약 person.name처럼 구조를 갖는다면 "(person.name)!myDefault"처럼 괄호로 감싸 주고요.

예제를 보면서 확인해 보겠습니다.

spring boot 프로젝트를 만들어 주고 편의상 template build 서비스를 하나 만들어 줍니다.

@Service
public class TemplateBuilderService {
    public String build(String templateString, Object dataModel) {
        try (StringWriter out = new StringWriter();
             StringReader reader = new StringReader(templateString)
        ) {
            Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            cfg.setLocale(Locale.KOREA);

            Template template = new Template("name", reader, cfg);

            template.process(dataModel, out);
            return out.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

이렇게 하면 템플릿 파일 없이 템플릿 문자열을 직접 넣어서 테스트해 볼 수 있습니다.

// Given
final String templateString = "Hello ${user}";
Map<String, String> model = Map.of("user", "world");

// When
String result = templateBuilderService.build(templateString, model);

// Then
then(result)
        .isEqualTo("Hello world");

이렇게 말이죠. ${user}에 world가 치환되어서 Hello world가 됩니다.

user에 null을 넣어 보겠습니다. Map.of로 하면 null값을 넣을 수 없어서 new HashMap방식으로 생성했습니다.

// Given
final String templateString = "Hello ${user}";

Map<String, Object> model = new HashMap<>() {{
    put("user", null);
}};

// When
final Throwable throwable = catchThrowable(() -> templateBuilderService.build(templateString, model));

// Then
then(throwable)
        .isInstanceOf(RuntimeException.class)
;

then(throwable.getCause())
        .isInstanceOf(InvalidReferenceException.class)
;

templateBuilderService.build에서 예외를 RuntimeException으로 감쌌기 때문에 RuntimeException이 발생하고 내부를 꺼내보면 InvalidReferenceException이 발생하는 것을 알 수 있습니다.

만약 user가 null인 경우 빈 값이라도 출력하고 싶다면 아래처럼 변수 뒤에 "!"를 붙이면 됩니다. ${user!}처럼요.

// Given
final String templateString = "Hello ${user!}";
Map<String, String> model = Map.of();

// When
String result = templateBuilderService.build(templateString, model);

// Then
then(result)
        .isEqualTo("Hello ");

 

만약 값이 없는 경우 기본값을 출력하고 싶다면 "!"뒤에 기본값을 넣어줍니다.

// Given
final String templateString = "Hello ${user!\"member\"}";
Map<String, String> model = Map.of();

// When
String result = templateBuilderService.build(templateString, model);

// Then
then(result)
        .isEqualTo("Hello member");

간단하죠?

 

조금은 복잡한 케이스를 살펴보겠습니다.

// Given
final String templateString = "지금은 ${date?string('yyyy-MM-dd a hh:mm:ss')}";
Map<String, Object> model = Map.of("date", new Date());

// When
String result = templateBuilderService.build(templateString, model);

// Then
System.out.println(result); // e.g. 지금은 2021-01-03 오후 05:01:56

날짜를 출력하는 예제입니다. 참고로 Locale.KOREA를 사용해서 "PM"대신 "오후"라고 나옵니다.

 

여기서 date가 null인 경우를 처리하려면 어떻게 해야 될까요?

// Given
final String templateString = "지금은 ${(date?string('yyyy-MM-dd a hh:mm:ss'))!}";
Map<String, Object> model = new HashMap<>() {{
    put("date", null);
}};

// When
String result = templateBuilderService.build(templateString, model);

// Then
System.out.println(result); // e.g. 지금은

소괄호"()"로 묶은 다음 "!"를 붙여주면 됩니다.

 

요약해 보면 null이 발생할 수 있는 값인 경우 변수 뒤에 "!"를 붙여 주면 빈 값으로 처리가 됩니다. "!"뒤에 기본값을 넣어 줄 수 있고요. 만약 변수에 부모가 있거나 메서드를 섞어 쓰고 있는 경우 전부 묶어서 소괄호"()"로 묶음 다음 뒤에 "!"를 붙여 주면 됩니다.

 

예제 코드: github.com/junho85/spring-freemarker-null

 

junho85/spring-freemarker-null

Contribute to junho85/spring-freemarker-null development by creating an account on GitHub.

github.com

 

반응형
Comments