DevLog ๐ถ
[Spring] Jackson annotation - Deserialization ์์๋ณด๊ธฐ ๋ณธ๋ฌธ
[Spring] Jackson annotation - Deserialization ์์๋ณด๊ธฐ
dolmeng2 2023. 4. 20. 18:54์ง๋ ๋ฒ์ ์ง๋ ฌํ ๊ด๋ จํด์ ๊ธ์ ์์ฑํ์์ด์, ์ด๋ฒ์๋ Jackson์์ ์ ๊ณตํ๋ ์ญ์ง๋ ฌํ ๊ธฐ๋ฅ์ ๋ํด์ ๊ณต๋ถํด๋ณด๊ณ ์ ํ๋ค.
โ๏ธ Deserialization
Deserialization is the process of reconstructing a data structure or object from a series of bytes or a string in order to instantiate the object for consumption.
์ญ์ง๋ ฌํ๋ data structure๋ ๊ฐ์ฒด๋ฅผ ์ผ๋ จ์ ๋ฐ์ดํธ๋ ๋ฌธ์์ด๋ก ์ฌ๊ตฌ์ฑํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก ์ธ์คํด์คํํ๋ ํ๋ก์ธ์ค์ด๋ค.
์ง๋ ฌํ์ ๋ฐ๋๋ผ๊ณ ๋ณผ ์ ์์ผ๋ฉฐ, ๋ณํ๋ ๋ฐ์ดํฐ๋ฅผ ์ฅ์น๊ฐ์ ์ ์ฅ์ด๋ ์ ์กํ๋ ๊ฒ์ด๋ค.
Jackson์์๋ ์ด๋ค ์ด๋ ธํ ์ด์ ์ ํตํด ์ญ์ง๋ ฌํ๋ฅผ ์ง์ํ๋์ง ํ์ธํด๋ณด์.
๐ฑ Jackson์ ๊ธฐ๋ณธ ์ญ์ง๋ ฌํ ์ ๋ต
์ง๋ ํฌ์คํ ์์, ์ง๋ ฌํ ์ getter๊ฐ ์์ ๊ฒฝ์ฐ public ํ๋์ ๋ํด ์ง๋ ฌํ๋ฅผ ํ๋ ๊ฒ์ ๋ณผ ์ ์์๋ค.
๊ทธ ์ธ์ ํ๋์ ๋ํด์๋ getter๊ฐ ์ด๋ ค ์์ด์ผ ํ๋๋ฐ, ์ญ์ง๋ ฌํ ์์๋ ๋ง์ฐฌ๊ฐ์ง๋ก getter๊ฐ ์ด๋ ค์์ผ๋ฉด ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก ์ญ์ง๋ ฌํ์ ๊ฒฝ์ฐ setter๋ฅผ ํตํด์๋ ์งํํ ์ ์๋๋ฐ, ์ฌ๋ฌ ๊ฐ์ง ์ผ์ด์ค๋ฅผ ํ ์คํธ ํด๋ณด์.
๋๋ฉ์ธ ๊ตฌ์กฐ
public class Crew {
public String publicName;
protected String protectedName;
String defaultName;
private String privateName;
...
}
ํ์ธ ์ฝ๋
Crew crew = new Crew("์ ธ๋", "์ ธ๋", "์ ธ๋", "์ ธ๋");
String result = new ObjectMapper().writeValueAsString(crew);
System.out.println(result);
String json = "{\"publicName\":\"์ ธ๋\", \"protectedName\":\"์ ธ๋\", \"defaultName\":\"์ ธ๋\", \"privateName\":\"์ ธ๋\"}";
Crew crew2 = new ObjectMapper().readerFor(Crew.class).readValue(json);
System.out.println(crew2);
1) Getter : ์ง๋ ฌํ O, ์ญ์ง๋ ฌํ X
2) Getter + ๊ธฐ๋ณธ ์์ฑ์ : ์ง๋ ฌํ O, ์ญ์ง๋ ฌํ O
3) Setter : public ํ๋๋ง ์ง๋ ฌํ O, ์ญ์ง๋ ฌํ X
4) Setter + ๊ธฐ๋ณธ ์์ฑ์ : public ํ๋๋ง ์ง๋ ฌํ O, ์ญ์ง๋ ฌํ O
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด getter๋ ์ง๋ ฌํ ์ ์ฉ, ๊ธฐ๋ณธ ์์ฑ์ + setter๋ ์ญ์ง๋ ฌํ ๋๋์ด๋ค.
๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์ ์ธํ๊ณ ๋ฆฌํ๋ ์ ์ ํตํด์ ๊ฐ์ฒด์ ๊ฐ์ ์ฑ์์ฃผ๋ ๊ฒ์ด๋ผ๊ณ ์ถ์ ๋๋ค.
์ ๋ง ๊ฐ๋ตํ๊ฒ ์ฝ๋๋ฅผ ํ์ธํด๋ดค๋๋ฐ, ์๋์ ๊ฐ์ด ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์์ฑํด์ฃผ๋ ์ฝ๋๊ฐ ์๋ค.
๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์์ฑํ๊ธฐ.
call()์ด๋ผ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ newInstance๋ฅผ ํตํด์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํด์ค๋ค.
์๊น ์์ ์ฝ๋(vanillaDeserialize)์์ beanProperties.find()๋ผ๋ ๋ฉ์๋๊ฐ ์์๋๋ฐ, ๊ฑฐ๊ธฐ์ deserializeAndSet()์ด๋ผ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ๊ทธ๋ฆฌ๊ณ valueDeserializer.deserialize() ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ฐ, ์๋ง ์ ๊ธฐ์... ๋ฆฌํ๋ ์ ์ ์ฐ๋ ๊ฒ ๊ฐ๋ค ใ ใ
์๋ฌดํผ! ์ ๋ฆฌํ์๋ฉด ์๋์ ๊ฐ์ด ๋ํ๋ผ ์ ์๋ค.
ํ์ | ์ง๋ ฌํ ๊ฐ๋ฅ ์ฌ๋ถ | ์ญ์ง๋ ฌํ ๊ฐ๋ฅ ์ฌ๋ถ |
Getter | O | X |
Getter + ๊ธฐ๋ณธ ์์ฑ์ | O | O |
Setter | public ํ๋๋ง | X |
Setter + ๊ธฐ๋ณธ ์์ฑ์ | public ํ๋๋ง | O |
๐ฑ @JsonSetter
Annotation that can be used to define a non-static, single-argument method to be used as a "setter" for a logical property as an alternative to recommended JsonProperty annotation; or (as of 2.9 and later), specify additional aspects of the assigning property a value during serialization.
JsonProperty ์ด๋ ธํ ์ด์ ์ ๋์ฒดํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
non-static์ด๋ฉฐ 1๊ฐ์ ์ธ์๋ฅผ ๊ฐ์ง ๋ฉ์๋์ ๋ํด์ ์ญ์ง๋ ฌํ ์ ์ฌ์ฉํ ์ ์๋ค.
๋ฐ์ธ๋ฉํ ๊ฐ์ฒด์ json์ ํ๋ ์ด๋ฆ์ด ์ผ์นํ์ง ์์ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ๋ค.
@JsonGetter์ ๋ง์ฐฌ๊ฐ์ง๋ก non-static ํด์ผ ํ๋ฉฐ, ์ค์ง 1๊ฐ์ argument๋ง ๋ฐ๋๋ค.
{
"name": "์ ธ๋",
"age": 23,
"menus": [
{"name":"๋ง๋ผํ", "price": 10000},
{"name":"์ฐ๋ญ", "price": 15000}
]
}
์์ ๊ฐ์ Json์ ๊ฐ์ฒด๋ก ๋ณ๊ฒฝํด๋ณธ๋ค๊ณ ์๊ฐํด๋ณด์.
์ด๋ ์ค์ ๊ฐ์ฒด์์๋ menus์ ๊ดํ ํ๋๊ฐ ์๊ณ , lunchMenus๋ผ๋ ํ๋๊ฐ ์์ด์ ๊ทธ์ชฝ์ผ๋ก ๋งคํ์ ์ํค๋ ค๊ณ ํ๋ค.
public class Menu {
private String name;
private int price;
public Menu() {
}
public void setName(final String name) {
this.name = name;
}
public void setPrice(final int price) {
this.price = price;
}
@Override
public String toString() {
return "Menu{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class Crew {
private String name;
private int age;
private List<Menu> lunchMenus;
public Crew() {
}
@JsonSetter("menus")
public void setLunchMenus(final List<Menu> lunchMenus) {
this.lunchMenus = lunchMenus;
}
public void setName(final String name) {
this.name = name;
}
public void setAge(final int age) {
this.age = age;
}
@Override
public String toString() {
return "Crew{" +
"name='" + name + '\'' +
", age=" + age +
", lunchMenus=" + lunchMenus +
'}';
}
}
์์ ๊ฐ์ด @JsonSetter๋ฅผ ํตํด ์ด๋ค ํ๋์ ๊ฐ์ด ์ฃผ์ ๋ ์ง ์ ํํ ์ ์๋ค.
์ค์ ๋ก ์ถ๋ ฅ์ ํด์ ํ์ธ์ ํด๋ณด์๋ค.
String json = "{\"name\": \"์ ธ๋\", " +
"\"age\": 23, " +
"\"menus\": [" +
"{\"name\":\"๋ง๋ผํ\", \"price\": 10000}, " +
"{\"name\":\"์ฐ๋ญ\", \"price\": 15000}]} ";
Crew crew = new ObjectMapper().readerFor(Crew.class).readValue(json);
์ค์ ๋ก ์ถ๋ ฅ์ ํด๋ณด๋ฉด ์์ ๊ฐ์ด ๋ชจ๋ lunchMenu๋ผ๋ ํ๋์ ์ ๋ค์ด๊ฐ ๊ฒ์ ๋ณผ ์ ์๋ค.
๐ฑ @JsonAnySetter
Marker annotation that can be used to define a logical "any setter" mutator -- either using non-static two-argument method (first argument name of property, second value to set) or a field (of type Map or POJO) - to be used as a "fallback" handler for all otherwise unrecognized properties found from JSON content. It is similar to XmlAnyElement in behavior; and can only be used to denote a single property per type.
If used, all otherwise unmapped key-value pairs from JSON Object values are added using mutator.
non-static, 2๊ฐ์ ์ธ์๋ฅผ ๊ฐ์ง ๋ฉ์๋๋ (์ฒซ ๋ฒ์งธ๋ ํ๋กํผํฐ ์ด๋ฆ, ๋ ๋ฒ์งธ๋ ์ค์ ํ ๊ฐ) ํน์ Map์ ์ธ์๋ก ๊ฐ์ง๊ณ ์๋ ๋ฉ์๋์ ๋ํด ์ญ์ง๋ ฌํ๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
Json ๊ฐ์ฒด ๊ฐ์์ ๋งคํ๋์ง ์์ ๋ชจ๋ ํค-๊ฐ ์์ด mutator๋ฅผ ์ฌ์ฉํ์ฌ ์ถ๊ฐ๋ ์ ์๋๋ก ํ๋ค.
Map์ ์ด์ฉํด ์ญ์ง๋ ฌํ๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก non-staticํ ๋ฉ์๋์ฌ์ผ ํ๋ฉฐ, ์ฒซ ๋ฒ์งธ ์ธ์๋ ํค, ๋ ๋ฒ์งธ ์ธ์๋ก value๋ฅผ ๋ฐ๋๋ค.
public class Wootecho {
private Map<String, String> crews = new HashMap<>();
public Map<String, String> getCrews() {
return crews;
}
@JsonAnySetter
public void addCrew(final String name, final String course) {
crews.put(name, course);
}
}
ํฌ๋ฃจ๋ค์ ๋ฆฌ์คํธ๋ฅผ ๋ด๋ ์ฐํ ์ฝ ํด๋์ค๊ฐ ์๋ค๊ณ ์๊ฐํด๋ณด์.
addCrew๋ฅผ ํตํด ์ด๋ฆ๊ณผ ํด๋น ํฌ๋ฃจ์ ๊ณผ์ ์ ๋ณด๋ฅผ ๋ฐ์์ map์ ์ถ๊ฐํด์ฃผ๋ ์ฝ๋์ด๋ค.
๊ทธ๋ฆฌ๊ณ , ์ฌ๊ธฐ์ @JsonAnySetter๋ฅผ ํ์ฉํ๋ฉด ์ญ์ง๋ ฌํ ๊ณผ์ ์์ json ํ๋กํผํฐ์ ๊ฐ์ ์ฝ๊ฒ map์ ๋ด์ ์ ์๋ค.
String json = "{" +
"\"ํฌ๋ฃจ1\":\"๋ฐฑ์๋\"," +
"\"ํฌ๋ฃจ2\":\"ํ๋ก ํธ์๋\"," +
"\"ํฌ๋ฃจ3\":\"์๋๋ก์ด๋\"" +
"}";
Wootecho wootecho = new ObjectMapper().readerFor(Wootecho.class).readValue(json);
Map<String, String> crews = wootecho.getCrews();
System.out.println(crews);
๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์คํํด๋ณด์.
๊ทธ๋ผ ์์ฒ๋ผ ๋งต์ ํฌ๋ฃจ๋ค์ ์ ๋ณด๊ฐ ์ ๋ด๊ธด ๊ฒ์ ํ์ธํ ์ ์๋ค.
๐ฑ @JsonCreator
Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.
์์ฑ์๋ ์ ์ ํฉํฐ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฐ๊ด๋ ํด๋์ค์ ์๋ก์ด ์ธ์คํด์ค๋ฅผ ์ธ์คํด์คํํ ๋ ์ฌ์ฉํ๋ ๋ง์ปค ์ด๋ ธํ ์ด์ ์ด๋ค.
NOTE: when annotating creator methods (constructors, factory methods), method must either be:
- Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
- Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to
์ฐธ๊ณ ๋ก, ์์ฑ์ ๋ฉ์๋์ ์ด๋ ธํ ์ด์ ์ ๋ฌ๊ธฐ ์ํด์๋ ํด๋น ๋ฉ์๋๊ฐ ๋ค์๊ณผ ๊ฐ์ ์ฌํญ ์ค ํ๋์ฌ์ผ ํ๋ค.
- ์ธ์์ ๋ํด @JsonProperty๊ฐ ์๋ ๋จ์ผ ์ธ์ ์์ฑ์, ํน์ ํฉํฐ๋ฆฌ ๋ฉ์๋ (๋ธ๋ฆฌ๊ฒ์ดํธ ์์ฑ์๋ผ๊ณ ํ๋ค)
: ์ด๋ Jackson์ Json์ ๋จผ์ argument์ ํ์ ์ ๋ฐ์ธ๋ฉํ๊ณ ์์ฑ์๋ฅผ ํธ์ถํ๋ฉฐ, jsonValue์ ์์ฃผ ์ฌ์ฉํ๋ค.
- ๋ฐ์ธ๋ฉํ ํ๋กํผํฐ ์ด๋ฆ์ ๋ํ๋ด๊ธฐ ์ํด, ๋ชจ๋ argument๋ jsonProperty ๋๋ JacksonInject๋ก ์ง์ ๋ ์์ฑ์๋ ํฉํฐ๋ฆฌ ๋ฉ์๋์ฌ์ผ ํ๋ค.
Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode.
One common use case is to use a delegating Creator to construct instances from scalar values (like java.lang.String) during deserialization, and serialize values using JsonValue.
๋ํ, ๋งค๊ฐ๋ณ์ ์ด๋ฆ์ ๊ฐ์งํ ์ ์๋ ํ์ฅ ๋ชจ๋์ ์ฌ์ฉํ์ง ์๋ ํ, ๋ชจ๋ @JsonProperty ์ด๋ ธํ ์ด์ ์ ์ค์ ์ด๋ฆ (๋น ๋ฌธ์์ด์ด ์๋)์ ์ง์ ํด์ผ ํ๋๋ฐ, JDK 1.8 ์ด์ ์๋ ๋ฐ์ดํธ์ฝ๋์์ ๋งค๊ฐ๋ณ์ ์ด๋ฆ์ ์ ์ฅ ๋ฐ ๊ฒ์์ด ๋์ง ์์์๋ค. (ํ์ง๋ง ๊ทธ ์ดํ๋ถํฐ๋ ๊ฐ๋ฅํ๊ฒ ๋๋ฉด์ ์ด๋ฆ ์ง์ ์ด ์ ํ ์ฌํญ์ด ๋์๋ค๊ณ ํ๋ค.)
์ผ๋ฐ์ ์ผ๋ก๋ ์ญ์ง๋ ฌํ ์ค์ delegating Creator๋ฅผ ์ฌ์ฉํ์ฌ String ๊ฐ์ ์ค์นผ๋ผ ๊ฐ์ผ๋ก ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ๊ณ , jsonValue๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ง๋ ฌํํ ๋ ๋ง์ด ์ฌ์ฉํ๋ค.
์์ฑ์๋ ์ ์ ํฉํฐ๋ฆฌ ๋ฉ์๋์์ ์ฃผ๋ก ์ฌ์ฉํ๋ฉฐ, ์์ฑ์์ ํ๋ ์ด๋ฆ์ด json์ ํ๋๊ฐ๊ณผ ๋ค๋ฅผ ๋ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
๋ณดํต @JsonProperty์ ์กฐํฉํ์ฌ ๋ง์ด ์ฌ์ฉํ๋ค.
public class Crew {
private String name;
private int age;
@JsonCreator
public Crew(
@JsonProperty("crewName") final String name,
@JsonProperty("crewAge") final int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Crew{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
์์ ๊ฐ์ ํฌ๋ฃจ ํด๋์ค๊ฐ ์๋ค๊ณ ํ์ ๋, ํด๋น ํฌ๋ฃจ ํด๋์ค์ ์์ฑ์์ @JsonCreator๋ฅผ ๋ถ์ฌ์ฃผ๊ณ , ์์ฑ์์ ์ธ์์ @JsonProperty๋ฅผ ๋ถ์ฌ์ค๋ค. ์ด๋ฌ๋ฉด ์์ฑ์์ ์ธ์๋ก ์ญ์ง๋ ฌํ ์ json ๊ฐ์ด ๋งคํ๋๋ค.
@Test
void test() throws JsonProcessingException {
// given
String json = "{\"crewName\": \"์ ธ๋\", " +
"\"crewAge\": 23} ";
// when
Crew crew = new ObjectMapper().readerFor(Crew.class).readValue(json);
System.out.println(crew);
}
์ค์ ๋ก ์ฝ๋๋ฅผ ๋๋ ค๋ณด๋ฉด ๊ต์ฅํ ์ ๋งคํ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๋ด๋ถ์ ์ธ์๊ฐ ๋ฆฌ์คํธ์ผ ๋๋ ์ ๋งคํ๋๋์ง ๊ถ๊ธํด์ ํด๋ดค๋ค.
public class Crew {
private String name;
private int age;
private List<Menu> lunchMenus;
@JsonCreator
public Crew(@JsonProperty("crewName") final String name,
@JsonProperty("crewAge") final int age,
@JsonProperty("menus") final List<Menu> lunchMenus) {
this.name = name;
this.age = age;
this.lunchMenus = lunchMenus;
}
...
}
๊ต์ฅํ ๋งคํ์ด ์ ๋๋ ๋ชจ์ต์ ๋ณผ ์ ์๋ค.
๐ฑ @JacksonInject
Jackson-specific annotation used for indicating that value of annotated property will be "injected", i.e. set based on value configured by ObjectMapper (usually on per-call basis). Usually property is not deserialized from JSON, although it possible to have injected value as default and still allow optional override from JSON.
์ด๋ ธํ ์ด์ ์ด ์ง์ ๋ ํ๋กํผํฐ์ ๊ฐ์ด ์ฃผ์ ๋ ๊ฒ์ด๋ผ๊ณ ์ฌ์ฉ๋๋ Jackson ์ ์ฉ ์ด๋ ธํ ์ด์ ์ด๋ค.
ObjectMapper๊ฐ ๊ตฌ์ฑํ ๊ฐ์ ๋ฐ๋ผ์ ์ค์ ๋๋ฉฐ, ์ผ๋ฐ์ ์ผ๋ก ํ๋กํผํฐ๋ Json์ผ๋ก๋ถํฐ ์ญ์ง๋ ฌํ๋์ง ์์ง๋ง, ๊ธฐ๋ณธ๊ฐ์ผ๋ก inject๋ ๊ฐ์ ์ฌ์ฉํ์ฌ Json์์ ์ ํ์ ์ผ๋ก ์ฌ์ ์๊ฐ ๊ฐ๋ฅํ๋ค.
@JacksonInject๋ฅผ ์ฌ์ฉํ๋ฉด Json ๋ฐ์ดํฐ์ ํ๋๊ฐ์ด ์๋ ๊ฐ์ ๋ํด์๋ inject๋ฅผ ์ํฌ ์ ์๋ค.
์ด๊ฑด ์ฝ๋๋ก ๋ณด๋ ๊ฒ ๋ ์ดํดํ๊ธฐ ์ฝ๊ธฐ ๋๋ฌธ์ ์ฝ๋๋ฅผ ํ์ธํด๋ณด์.
public class Crew {
@JacksonInject
private Long id;
private String name;
private int age;
public Crew() {
}
public void setName(final String name) {
this.name = name;
}
public void setAge(final int age) {
this.age = age;
}
@Override
public String toString() {
return "Crew{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
์์ ๊ฐ์ด id๋ผ๋ ํ๋๋ฅผ ์ถ๊ฐํด์ฃผ์๋ค. ์ด ๊ฐ์ json์ผ๋ก ๋ค์ด์ค์ง ์์ ๊ฐ์ด๋ค.
String json = "{\"name\": \"์ ธ๋\", " +
"\"age\": 23} ";
InjectableValues inject = new InjectableValues.Std()
.addValue(Long.class, 1L);
Crew crew = new ObjectMapper().reader(inject)
.forType(Crew.class)
.readValue(json);
System.out.println(crew);
๊ทธ๋ฆฌ๊ณ , ์์ ๊ฐ์ด InjectableValues๋ฅผ ํ์ฉํ์ฌ์ ๊ฐ์ ์ฃผ์ ํด์ฃผ์๋ค.
๊ทธ๋ผ ์ด๋ฐ ์์ผ๋ก 1์ด ๋ค์ด๊ฐ๋ค! (์ ๊ธฐํ๋ค)
์ด๋ค ์ํฉ์์ ์์ฉํ ์ ์์์ง ์๊ฐํด๋ดค๋๋ฐ, ์์ฒญ์ด ๋ค์ด์จ ์๊ฐ์ ๋ํด ํ๋์ ์ ์ฅํ ๋ ์ฌ์ฉํ ์๋ ์์ ๊ฒ ๊ฐ๊ณ (๋ฌผ๋ก ๊ทธ๋ฅ LocalDateTime์ ์ธ ์๋ ์๊ฒ ์ง๋ง?) DB ํ๋์ ๋ง์ง๋ง ์ ๋ฐ์ดํธ ์๊ฐ ๋ฑ์ผ๋ก ํ์ฉํ ์ ์์ ๊ฒ ๊ฐ๋ค. (๋ฌผ๋ก ์ด๊ฒ๋ JpaAuditing ์ฌ์ฉํ๋ฉด ๋๊ธด ํ๊ฒ ์ง๋ง...)
์๋ฌดํผ ์ญ์ง๋ ฌํ์ ๋ํด์ ์์๋ณด์๋ค. ๋ค์์๋ ObjectMapper์ ๋์์๋ฆฌ๋ ์ข ํ์ธํด๋ณผ ์์ ์ด๋ค.