반응형
0. 목차
목차
1. Jackson 직렬화 Annotations
- 직렬화 주석
1.1 @JsonAnyGetter
- Map 필드를 사용할 때 유용하게 사용할 수 있음
- Map에 있는 값들을 일반 필드 속성처럼 표시할 수 있음
@Test
fun jsonAnyGetterTest() {
class ExtendableBean(val name: String) {
@get:JsonAnyGetter
val properties: MutableMap<String, String> = mutableMapOf()
}
val bean = ExtendableBean("My bean")
bean.properties["attr1"] = "val1"
bean.properties["attr2"] = "val2"
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).doesNotContain("properties")
assertThat(result).contains("attr1")
assertThat(result).contains("attr2")
}
- 출력 값을 아래와 같다.
- @JsonAnyGetter를 사용하였을 때
{"name":"My bean","attr1":"val1","attr2":"val2"}
- @JsonAnyGetter를 사용하지 않았을 때
{"name":"My bean","properties":{"attr1":"val1","attr2":"val2"}}
- @JsonAnyGetter(enabled = false)와 같은 형태로 비활성화도 가능
1.2 @JsonGetter
- 지정한 메소드를 getter메서드로 사용하기 위해 사용
- @JsonProperty 주석의 대안
@Test
fun jsonGetterTest() {
class MyBean(val name: String) {
@JsonGetter("name")
fun getMyName(): String {
return "My name is $name"
}
}
val bean = MyBean("My bean")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("My name is")
}
- 출력 결과
{"name":"My name is My bean"}
1.3 @JsonPropertyOrder
- 직렬화 시 속성의 순서를 지정할 수 있음
@Test
fun jsonPropertyOrderTest() {
@JsonPropertyOrder("country", "name", "age")
class MyBean(val name: String, val age: Int, val country: String)
val bean = MyBean("My bean", 32, "KR")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("My bean")
assertThat(result).contains(32.toString())
assertThat(result).contains("KR")
}
- 출력 결과
{"country":"KR","name":"My bean","age":32}
- @JsonPropertyOrder(alphabetic = true)를 사용해 알파벳 순으로도 정렬할 수 있음
{"age":32,"country":"KR","name":"My bean"}
1.4 @JsonRawValue
- 속성을 그대로 직렬화 하여 JSON 형태로 사용할 수 있다.
@Test
fun jsonRawValueTest() {
class MyBean(val name: String, @JsonRawValue val json: String)
val bean = MyBean("My bean", "{\"attr\":false}")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("{\"attr\":false}")
}
- 출력 결과 (사용)
{"name":"My bean","json":{"attr":false}}
- 출력 결과 (미사용)
{"name":"My bean","json":"{\"attr\":false}"}
1.5 @JsonValue
- 전체 인스턴스를 직렬 화하는 데 사용할 단일 메서드를 지정
1.5.1 일반 클래스에서 사용
@Test
fun jsonValueTest_class() {
class MyBean(val name: String, val age: Int, val country: String) {
@JsonValue
fun getMyBean(): String {
return "MyBean(name='$name', age=$age, country='$country')"
}
}
val bean = MyBean("My bean", 32, "KR")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("My bean")
assertThat(result).contains(32.toString())
assertThat(result).contains("KR")
}
- 출력 결과 (사용)
"MyBean(name='My bean', age=32, country='KR')"
- 출력 결과 (미사용)
{"name":"My bean","age":32,"country":"KR","myBean":"MyBean(name='My bean', age=32, country='KR')"}
1.5.2. 열거형 타입에서 사용
@Test
fun jsonValueTest_enum() {
val result = ObjectMapper().writeValueAsString(Type.TYPE_A)
print(result)
assertThat(result).contains("Type A")
}
enum class Type(val id: Int, private val typeName: String) {
TYPE_A(1, "Type A"),
TYPE_B(1, "Type B");
@JsonValue
fun getName(): String {
return typeName
}
}
- 출력 결과 (사용)
"Type A"
- 출력 결과 (미사용)
"TYPE_A"
1.6 @JsonRootName
- Json을 한번 더 감싸는 root wrapper 이름을 설정할 수 있음
- ObjectMapper에 SerializationFeature.WRAP_ROOT_VALUE 를 설정해주어야 한다.
@Test
fun jsonRootNameTest() {
@JsonRootName(value = "bean")
class MyBean(val name: String, val age: Int, val country: String)
val bean = MyBean("My bean", 32, "KR")
val mapper = ObjectMapper()
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE)
val result = mapper.writeValueAsString(bean)
print(result)
assertThat(result).contains("bean")
assertThat(result).contains("My bean")
assertThat(result).contains(32.toString())
assertThat(result).contains("KR")
}
- 출력 결과
{"bean":{"name":"My bean","age":32,"country":"KR"}}
- 주의할점은 SerializationFeature.WRAP_ROOT_VALUE 설정 후에 @JsonRootName을 설정하지 않으면 기본적으로 클래스의 이름으로 root 이름이 정의가 된다.
// @JsonRootName 사용, SerializationFeature.WRAP_ROOT_VALUE 적용
{"bean":{"name":"My bean","age":32,"country":"KR"}}
// @JsonRootName 미사용, SerializationFeature.WRAP_ROOT_VALUE 적용
{"MyBean":{"name":"My bean","age":32,"country":"KR"}}
1.7 @JsonSerialize
- 엔티티를 마샬링 할 때 사용할 사용자 custom serialzer를 지정할 수 있게 해 줌
- StdSerializer를 상속받아 custom serialzer를 작성한다.
- 사용자 custom serialzer
class CustomDateSerializer(t: Class<Date>? = null) : StdSerializer<Date>(t) {
companion object {
private val formatter = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
}
@Throws(IOException::class, JsonProcessingException::class)
override fun serialize(
value: Date,
generator: JsonGenerator,
provider: SerializerProvider
) {
generator.writeString(formatter.format(value))
}
}
- 사용 예제
@Test
fun jsonSerializeTest() {
class MyBean(
val name: String,
@get:JsonSerialize(using = CustomDateSerializer::class) val date: Date)
val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
val toParse = "03-08-2022 12:18:00"
val date = df.parse(toParse)
val bean = MyBean("My bean", date)
val mapper = ObjectMapper()
val result = mapper.writeValueAsString(bean)
print(result)
assertThat(result).contains(toParse)
}
- 출력 결과
{"name":"My bean","date":"03-08-2022 12:18:00"}
2. Jackson 역직렬화 Annotations
- 역직렬화 주석
2.1 @JsonCreator
- 역직렬화시에 entity는 변경하지 않고 언마샬링 할 수가 있음
- 역직렬화 대상 Json
{
"id":1,
"theName":"My bean"
}
- 사용 예제
@Test
fun jsonCreatorTest() {
class BeanWithCreator @JsonCreator constructor(
@JsonProperty("id") val id: Int,
@JsonProperty("theName") val name: String
) {
override fun toString(): String {
return "BeanWithCreator(id=$id, name='$name')"
}
}
val json = "{\"id\":1,\"theName\":\"My bean\"}"
val mapper = ObjectMapper()
val result = mapper.readerFor(BeanWithCreator::class.java)
.readValue<BeanWithCreator>(json)
print(result)
assertThat(result.name).isEqualTo("My bean")
}
2.2 @JacksonInject
- 어노테이션을 붙인 필드는 json 역직렬화를 통한 데이터가 아닌 inject를 해서 값을 가져올 때 사용
- 사용 예제
@Test
fun jacksonInjectTest() {
class BeanWithInject () {
@JacksonInject("id1")
val id1: Int? = null
@JacksonInject("id2")
val id2: Long? = null
val name: String? = null
override fun toString(): String {
return "BeanWithInject(id1=$id1, id2=$id2, name=$name)"
}
}
val json = "{\"name\":\"My bean\"}"
val mapper = ObjectMapper()
val inject = InjectableValues.Std()
.addValue("id1", 1)
.addValue("id2", 2L)
val result = mapper.reader(inject)
.forType(BeanWithInject::class.java)
.readValue<BeanWithInject>(json)
print(result)
assertThat(result.name).isEqualTo("My bean")
assertThat(result.id1).isEqualTo(1)
assertThat(result.id2).isEqualTo(2L)
}
- 보통 인터넷 예제들을 보면 addValue(Class<?> classKey, Object value) 메서드를 사용해 inject 하는 걸 볼 수 있음
- 개인적으로 필드 이름을 명시적으로 적어서 inject 하는 게 더 좋다고 생각
2.3 @JsonAnySetter
- JsonAnyGetter와 반대
- 역직렬화시 정해지지 않은 json 필드들을 Map의 값으로 사용 가능
- 역직렬화 대상 Json
{
"name":"My bean",
"attr2":"val2",
"attr1":"val1"
}
- 사용 예제
@Test
fun jsonAnySetterTest() {
class ExtendableBean {
val name: String? = null
val properties: MutableMap<String, String> = mutableMapOf()
@JsonAnySetter
fun add(key: String, value: String) {
properties[key] = value
}
override fun toString(): String {
return "ExtendableBean(name=$name, properties=$properties)"
}
}
val json = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}"
val mapper = ObjectMapper()
val result = mapper.readerFor(ExtendableBean::class.java)
.readValue<ExtendableBean>(json)
print(result)
assertThat(result.name).isEqualTo("My bean")
assertThat(result.properties).containsEntry("attr2", "val2")
assertThat(result.properties).containsEntry("attr1", "val1")
}
2.4 @JsonSetter
- 어노테이션이 붙은 메서드를 필드의 setter 메서드로 사용
- @JsonProperty 대안으로 사용 가능
- 사용 예제
@Test
fun jsonSetterTest() {
class MyBean {
var myName: String? = null
val id: Int? = null
@JsonSetter("name")
fun setTheName(theName: String){
this.myName = theName
}
override fun toString(): String {
return "MyBean(myName=$myName, id=$id)"
}
}
val json = "{\"id\":1,\"name\":\"My bean\"}"
val mapper = ObjectMapper()
val result = mapper.readerFor(MyBean::class.java)
.readValue<MyBean>(json)
print(result)
assertThat(result.myName).isEqualTo("My bean")
assertThat(result.id).isEqualTo(1)
}
2.5 @JsonDeserialize
- custome deserializer를 작성한다.
- Date 타입 필드를 역직렬화 하는 경우가 있다고 가정
- 사용 예제 (custome deserializer)
class CustomDateDeserializer(t: Class<Date>? = null): StdDeserializer<Date>(t) {
companion object {
private val formatter = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
}
@Throws(IOException::class, ParseException::class)
override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): Date {
val date = jsonParser.text
return formatter.parse(date)
}
}
- 사용 예제
@Test
fun jsonDeserializeTest() {
class EventWithSerializer {
var name: String? = null
@JsonDeserialize(using = CustomDateDeserializer::class)
var eventDate: Date? = Date()
}
val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
val json = "{\"name\":\"demo\",\"eventDate\":\"20-12-2014 02:30:00\"}"
val mapper = ObjectMapper()
val result = mapper.readerFor(EventWithSerializer::class.java)
.readValue<EventWithSerializer>(json)
print(result)
assertThat(result.name).isEqualTo("demo")
assertThat(df.format(result.eventDate)).isEqualTo("20-12-2014 02:30:00")
}
2.6 @JsonAlias
- 역직렬화시 속성에 대한 하나 이상의 대체 이름을 지정함
- 사용 예제
@Test
fun jsonAliasTest() {
class AliasBean {
@JsonAlias(value = ["fName", "f_name"])
var firstName: String? = null
var lastName: String? = null
override fun toString(): String {
return "AliasBean(firstName=$firstName, lastName=$lastName)"
}
}
val json = "{\"fName\": \"version\", \"lastName\": \"demo\"}"
val mapper = ObjectMapper()
val result = mapper.readerFor(AliasBean::class.java)
.readValue<AliasBean>(json)
print(result)
assertThat(result.firstName).isEqualTo("version")
}
3. Jackson 속성 관련 Annotations
3.1 @JsonIgnoreProperties
- 무시할 속성 또는 속성 목록을 표시하는 클래스 단위 어노테이션
- 사용 예제
@Test
fun jsonIgnorePropertiesTest() {
@JsonIgnoreProperties(value = ["firstName"])
class BeanWithIgnore(
var firstName: String? = null,
var lastName: String? = null
) {
override fun toString(): String {
return "AliasBean(firstName=$firstName, lastName=$lastName)"
}
}
val bean = BeanWithIgnore("jaehyun", "lim")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("lim")
assertThat(result).doesNotContain("jaehyun")
}
3.2 @JsonIgnore
- 필드 수준에서 무시할 속성을 지정
- 사용 예제
@Test
fun jsonIgnoreTest() {
class BeanWithIgnore(
@JsonIgnore
var firstName: String? = null,
var lastName: String? = null
) {
override fun toString(): String {
return "AliasBean(firstName=$firstName, lastName=$lastName)"
}
}
val bean = BeanWithIgnore("jaehyun", "lim")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("lim")
assertThat(result).doesNotContain("jaehyun")
}
3.3 @JsonIgnoreType
- 어노테이션을 적용한 클래스의 모든 속성을 무시
- 사용 예제
@Test
fun jsonIgnoreTypeTest() {
@JsonIgnoreType
class Name(
var firstName: String? = null,
var lastName: String? = null
) {
override fun toString(): String {
return "Name(firstName=$firstName, lastName=$lastName)"
}
}
class User(
var id: String,
var name: Name
) {
override fun toString(): String {
return "User(id='$id', name=$name)"
}
}
val bean = User("1", Name("jaehyun", "lim"))
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("1")
assertThat(result).doesNotContain("jaehyun")
assertThat(result).doesNotContain("lim")
}
3.4 @JsonInclude
- empty / null / default값이 있는 속성을 제외할 수 있음
- 기본 사용법과 custom을 설정해서 사용 가능
- 사용 예제 (NON_NULL)
@Test
fun jsonIncludeTest_NON_NULL() {
@JsonInclude(value = JsonInclude.Include.NON_NULL)
class MyBean(
var firstName: String? = null,
var lastName: String? = null
) {
override fun toString(): String {
return "MyBean(firstName=$firstName, lastName=$lastName)"
}
}
val bean = MyBean("jaehyun", null)
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("jaehyun")
assertThat(result).doesNotContain("lim")
}
- 사용 예제(CUSTOM)
@Test
fun jsonIncludeTest_CUSTOM() {
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
class MyBean(
var firstName: String? = null,
var lastName: String? = null,
@get:JsonInclude(content = JsonInclude.Include.CUSTOM, contentFilter = PhoneNumberFilter::class)
var privateInfo: Map<String, String> = mutableMapOf(),
@get:JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = CompanyNameFilter::class)
var companyName: String? = null,
) {
override fun toString(): String {
return "MyBean(firstName=$firstName, lastName=$lastName, privateInfo=$privateInfo, companyName=$companyName)"
}
}
val bean = MyBean(
"jaehyun",
"",
mapOf("phoneNumber" to "010-1111-2222", "address" to "seoul"),
"NAVER"
)
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("firstName")
assertThat(result).contains("phoneNumber")
assertThat(result).doesNotContain("lastName")
assertThat(result).doesNotContain("companyName")
}
class PhoneNumberFilter {
private val phoneNumberPattern: Pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}")
override fun equals(other: Any?): Boolean {
if (other == null || other !is String) {
return false
}
return !phoneNumberPattern.matcher(other.toString()).matches()
}
override fun hashCode(): Int {
return phoneNumberPattern.hashCode()
}
}
class CompanyNameFilter {
override fun equals(other: Any?): Boolean {
if (other == null || other !is String) {
return false
}
return !other.contentEquals("LINE")
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
- 결과
{"firstName":"jaehyun","privateInfo":{"phoneNumber":"010-1111-2222"}}
3.5 @JsonAutoDetect
- 속성의 표시 여부에 관한 전반적인 정의를 할 수 있다.
- @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)로 지정한다면 접근 지정자 레벨이 private이라도 표시
- JsonAutoDetect.Visibility.ANY 외에도 PUBLIC_ONLY, PROTECTED_AND_PUBLIC등을 설정할 수 있다.
- 사용 예제
@Test
fun jsonAutoDetectTest() {
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
class MyBean(
private var firstName: String? = null,
var lastName: String? = null
) {
fun getFullName(): String {
return "$firstName $lastName"
}
override fun toString(): String {
return "MyBean(firstName=$firstName, lastName=$lastName)"
}
}
val bean = MyBean("jaehyun", "lim")
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("firstName")
assertThat(result).contains("lastName")
}
4. Jackson 다형성 Type 관련 Annotations
- 다형성 처리를 위한 annotation
- @JsonTypeInfo – 실제 클래스가 무엇인지 부가 정보 표시를 위한 annotation
- @JsonSubTypes – 서브 클래스들을 모두 등록해주어야 사용 가능
- @JsonTypeName – annotation이 달린 클래스에 사용할 이름을 정의
- 사용 class
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
JsonSubTypes.Type(Dog::class, name = "dog"),
JsonSubTypes.Type(Cat::class, name = "cat")
)
open class Animal {
var name: String? = null
}
@JsonTypeName("dog")
class Dog : Animal() {
var barkVolume = 0L
override fun toString(): String {
return "Dog(name='$name', barkVolume=$barkVolume)"
}
}
@JsonTypeName("cat")
class Cat : Animal() {
var isNeutering = true
override fun toString(): String {
return "Cat(name='$name', isNeutering=$isNeutering)"
}
}
- 사용 예제 (serialization)
@Test
fun jsonPolymorphicTest_Serialization() {
val dog = Dog().apply {
name = "dog"
barkVolume = 10L
}
val cat = Cat().apply {
name = "cat"
isNeutering = true
}
var result = ObjectMapper().writeValueAsString(dog)
print(result)
assertThat(result).contains("type")
assertThat(result).contains("dog")
result = ObjectMapper().writeValueAsString(cat)
print(result)
assertThat(result).contains("type")
assertThat(result).contains("cat")
}
- JsonTypeInfo.Id.CLASS를 사용하면 실제 {pacakge 이름}.{class 이름} 형태로 사용할 수 있다.
- property에는 사용할 type을 지정할 수 있다.
- JsonTypeInfo.As.EXISTING_PROPERTY 를 사용해서 이미 가지고 있는 프로퍼티에 type을 표시할 수 있다.
- 결과
{"type":"dog","name":"dog","barkVolume":10}{"type":"cat","name":"cat","neutering":true}
- 사용 예제 (deserialization)
@Test
fun jsonPolymorphicTest_Deserialization() {
val json = "{\"name\":\"lacy\",\"type\":\"dog\",\"barkVolume\":10}"
val result = ObjectMapper().readValue(json, Animal::class.java)
print(result)
assertThat(result).isInstanceOf(Dog::class.java)
}
- 결과
Dog(name='lacy', barkVolume=10)
5. 기타 일반적인 Jackson Annotations
5.1 @JsonProperty
- json 속성 이름을 나타냄
- getXXX, setXXX 외 지정하고 싶은 gettter, setter에 사용 가능
- 사용 예제
@Test
fun jsonPropertyTest() {
class MyBean{
var id: String? = null
var name: String? = null
@JsonProperty("name")
fun setTheName(theName: String){
this.name = theName
}
@JsonProperty("name")
fun getTheName(): String? {
return this.name
}
override fun toString(): String {
return "MyBean(id='$id', name='$name')"
}
}
var bean = MyBean().apply {
id = "1"
name = "demo"
}
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("id")
assertThat(result).contains("1")
assertThat(result).contains("name")
assertThat(result).contains("demo")
bean = ObjectMapper().readValue(result, MyBean::class.java)
print(result)
assertThat(bean.id).contains("1")
assertThat(bean.name).contains("demo")
}
5.2. @JsonFormat
- 날짜/시간 값을 직렬화할 때 형식을 지정
- 사용 예제
@Test
fun jsonFormatTest() {
class MyBean(
var name: String,
@get:JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "dd-MM-yyyy hh:mm:ss"
)
var date: Date
) {
override fun toString(): String {
return "MyBean(name='$name', date=$date)"
}
}
val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
df.timeZone = TimeZone.getTimeZone("UTC")
val toParse = "15-08-2022 01:00:00"
val parse = df.parse(toParse)
val bean = MyBean("party", parse)
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains(toParse)
}
5.3 @JsonUnwrapped
- 직렬화 / 역직렬화시에 unwrapped/flatten 되어야 하는 값을 정의
- 사용 예제
class UnwrappedUser(
val id: Int,
@get:JsonUnwrapped
var name: Name
) {
override fun toString(): String {
return "UnwrappedUser(id=$id, name=$name)"
}
}
class Name(
val firstName: String,
val lastName: String
) {
override fun toString(): String {
return "Name(firstName='$firstName', lastName='$lastName')"
}
}
@Test
fun jsonUnwrappedTest() {
val bean = UnwrappedUser(1, Name("demo", "lim"))
val result = ObjectMapper().writeValueAsString(bean)
print(result)
assertThat(result).contains("demo")
assertThat(result).contains("1")
assertThat(result).doesNotContain("name")
}
5.4 @JsonView
- 동일한 객체에서도 표현할 프로퍼티를 여러 조합으로 선택할 수 있게 한다.
- 예를 들어서 ID와 PW가 존재하는 객체에서 일반 User 와 Admin에게 보여줘야 할 데이터는 다를 수 있다.
- 사용 예제
class View {
class User
class Admin
}
class User {
@JsonProperty("user_id")
@JsonView(View.User::class, View.Admin::class)
var userId: Long? = null
@JsonProperty("password")
@JsonView(View.Admin::class)
var password: String? = null
}
@Test
fun jsonViewTest() {
val bean = User()
.apply {
this.userId = 10L
this.password = "1234"
}
var result = ObjectMapper()
.writerWithView(View.User::class.java)
.writeValueAsString(bean)
print(result)
assertThat(result).contains("user_id")
assertThat(result).doesNotContain("password")
result = ObjectMapper()
.writerWithView(View.Admin::class.java)
.writeValueAsString(bean)
print(result)
assertThat(result).contains("user_id")
assertThat(result).contains("password")
}
5.4 @JsonManagedReference, @JsonBackReference
- 객체와 객체가 1:N 과 같이 관계를 가지고 있고, json 변환 시 순환 참조가 문제가 될 때 사용
- 사용 예제
class Parent {
var id: Int? = null
@JsonManagedReference
var children: MutableList<Child> = mutableListOf()
override fun toString(): String {
return "Parent(id=$id, children=$children)"
}
}
class Child {
var id: Int? = null
@JsonBackReference
var parent: Parent? = null
override fun toString(): String {
return "Child(id=$id, parent=$parent)"
}
}
@Test
fun jsonReferenceTest() {
val parent = Parent().apply {
this.id = 1
val child1 = Child().apply {
this.id = 1
}
val child2 = Child().apply {
this.id = 2
}
this.children.add(child1)
this.children.add(child2)
child1.parent = this
child2.parent = this
}
val result = ObjectMapper()
.writeValueAsString(parent)
print(result)
assertThat(result).contains("children")
}
- 해당 어노테이션없이 사용한다면 아래와 같은 순환 참조 에러를 볼 수 있다.
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain...
5.5 @JsonIdentityInfo
- @JsonManagedReference, @JsonBackReference와 유사하게 직렬화/역직렬화시 순환 참조를 막을 수 있다.
- ID를 통해서 순환참조 방지
- 사용 예제
@JsonIdentityInfo(generator = PropertyGenerator::class, property = "id")
class ItemWithIdentity {
var id = 0
var itemName: String? = null
var owner: UserWithIdentity? = null
override fun toString(): String {
return "ItemWithIdentity(id=$id, itemName=$itemName, owner=$owner)"
}
}
@JsonIdentityInfo(generator = PropertyGenerator::class, property = "id")
class UserWithIdentity {
fun addItem(item: ItemWithIdentity) {
userItems.add(item)
}
override fun toString(): String {
return "UserWithIdentity(id=$id, name=$name, userItems=$userItems)"
}
var id = 0
var name: String? = null
var userItems: MutableList<ItemWithIdentity> = mutableListOf()
}
@Test
@Throws(JsonProcessingException::class)
fun jsonIdentityInfoTest() {
val user = UserWithIdentity().apply {
id = 1
name = "demo"
}
val item = ItemWithIdentity().apply {
id = 2
itemName = "book"
owner = user
}
user.addItem(item)
val result = ObjectMapper().writeValueAsString(user)
print(result)
assertThat(result).contains("userItems")
assertThat(result).contains("owner")
}
- 결과
{"id":1,"name":"demo","userItems":[{"id":2,"itemName":"book","owner":1}]}
5.6 @JsonFilter
- 직렬화 중 사용할 필터를 지정
- 사용 예제
@Test
fun jsonFilterTest() {
@JsonFilter("myFilter")
class BeanWithFilter(
var id: Int,
var name: String?
) {
override fun toString(): String {
return "BeanWithFilter(id=$id, name=$name)"
}
}
val bean = BeanWithFilter(1, "demo")
val filters = SimpleFilterProvider().addFilter(
"myFilter",
SimpleBeanPropertyFilter.filterOutAllExcept("name")
)
val result = ObjectMapper()
.writer(filters)
.writeValueAsString(bean)
print(result)
assertThat(result).contains("name")
assertThat(result).doesNotContain("id")
}
6. Custom Jackson Annotation
- @JacksonAnnotationsInside annotation을 사용해서 Custom 한 jackson annotation을 만들 수 있음
- 사용 예제
// CustomAnnotation.java
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"name", "id", "dateCreated"})
public @interface CustomAnnotation {
}
@Test
fun jacksonAnnotationsInsideTest() {
@CustomAnnotation
class BeanWithCustomAnnotation(
var id: Int,
var name: String?,
var dateCreated: Date?
) {
override fun toString(): String {
return "BeanWithCustomAnnotation(id=$id, name=$name, dateCreated=$dateCreated)"
}
}
val bean = BeanWithCustomAnnotation(1, "demo", null)
val result = ObjectMapper()
.writeValueAsString(bean)
print(result)
assertThat(result).doesNotContain("dateCreated")
assertThat(result).contains("id")
assertThat(result).contains("name")
}
- null 값을 제외하는 @JsonInclude / 프로퍼티 순서를 정하는 @JsonPropertyOrder를 사용
- 결과
// 미사용
{"id":1,"name":"demo","dateCreated":null}
// 사용
{"name":"demo","id":1}
7.Jackson MixIn Annotations
- 특정 타입의 속성을 무시할 때 MixIn annotation을 사용한다.
- 사용 예제
class Employee(
val name: String,
val age: Int,
val address: Address
)
class Address(
val address1: String,
val address2: String
)
@JsonIgnoreType
class MyMixInForIgnoreType {}
@Test
fun mixInTest() {
val bean = Employee("demo", 32, Address("add1", "add2"))
var result = ObjectMapper()
.writeValueAsString(bean)
print(result)
assertThat(result).contains("name")
assertThat(result).contains("age")
assertThat(result).contains("address")
val mapper = ObjectMapper()
mapper.addMixIn(Address::class.java, MyMixInForIgnoreType::class.java)
result = mapper.writeValueAsString(bean)
assertThat(result).contains("name")
assertThat(result).contains("age")
assertThat(result).doesNotContain("address")
}
8.Disable Jackson Annotation
- MapperFeature.USE_ANNOTATIONS을 사용해서 모든 jackson annotaion을 비활성화시킬 수 있음
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_ANNOTATIONS);
반응형