Jackson 을 이용한 object serializing, deserializing 에서, 프로퍼티의 값을 원하는대로 바꿔 사용할 수 있다.

1. 원본 프로퍼티에는 @JsonIgnore 를 지정한다.

2. serializing 에 사용될 값에는 @JsonProperty fun 을 사용한다.

3. deserializing 에 사용될 값에는 @set:JsonProperty 를 사용한다.

 

예:

Item 객체를 포함하는 MySomething 객체 인스턴스를 Json <-> Object 변환 시, Item 을 그대로 내보내기보다는 item 의 id 만을 내보내고, 받고 싶은 경우:

class MySomething {
	
    @JsonIgnore
    lateinit var item: Item
    
    @JsonProperty("itemRef")
    fun itemId() = item.id
    
    @set:JsonProperty("itemRef")
    lateinit var itemRef: String
}

 

* item: Item 프로퍼티에 lateinit 을 사용하는 이유:

serializing 에서는 val 를 사용해도 상관 없겠지만, deserializing 에서는 val 를 사용할 수 없다. Json 에서 이 프로퍼티는 다음과 같이 변환될 것인데, deserializing 에서 이걸 가지고 다시 Item 으로 변환할 수 없다.(따로 deserializer 를 정의하면 가능하겠지만 이는 논외로 한다.)

"itemRef":"<item_id>"

위의 이유로 deserializing 시에 item 프로퍼티는 초기화되지 못하여(위의 "itemRef" 는 itemRef: String 프로퍼티에 세팅된다.) val 사용 시 deserializing 에서의 인스턴스 생성 시점에 예외가 발생한다.

 

* itemRef: String 프로퍼티에 lateinit 을 사용하는 이유:

위와 비슷하다. itemRef 는 deserializing 을 위한 것으로, 이외의 상황에서 사용되진 않는다고 상정했기 때문에 평소에는 굳이 초기화할 필요 없도록 남겨두고 위해서이다. (val 를 사용하면 반드시 초기화해야 하고, json 변환이 필요하지 않은 상황에도 영향을 주게 된다.)

 

* fun itemId() 를 사용하는 이유:

아래와 같이 사용해도 serializing 시에 같은 결과가 나온다:

@get:JsonProperty("xxxRef")
val itemId = item.id

 

하지만 이렇게하면 deserializing 시에 item property not initialized exception 을 만나게 된다. 이는 코틀린에 관한 것인데, val 프로퍼티는 인스턴스 생성 과정에서 시점에 초기화가 이루어진다. val itemId 프로퍼티 초기화 시 item.id 를 참조하게 되는데, item 프로퍼티는 아직 초기화되지 못한 상태이다. 때문에 예외가 발생한다. 이를 방지하고자 fun 을 사용한다.

'기술 일반 > 일반' 카테고리의 다른 글

CLOB 컬럼 핸들링 중 인코딩 문제  (0) 2019.03.08
유니코드와 UTF-8, UTF-16  (0) 2017.04.07

1. DB 인코딩 설정이 실제 데이터의 인코딩을 보장하지 않는다.

2. DB 인코딩에서 벗어난 데이터가 존재할 수 있다.


======

CLOB 컬럼은 데이터를 단일 바이트 문자 스트림으로 취급한다. 때문에 값을 넣는 프로세스에 따라 현재 DB 인코딩과 다른 문자 바이트가 입력될 수 있다.


DB 설정의 인코딩이란 이 DB 안의 데이터가 항상 그 인코딩되어 있다는 의미가 아니다. CLOB 컬럼에는 내부적으로 바이트 데이터를 취급하며, 이 바이트를 어떤 규칙으로 해석해서 텍스트로 표현(표시)하느냐를 결정하는 것이 인코딩이다. 


DB 인코딩이 UTF-8 로 설정되어 있어도 UTF-8 범위를 벗어난 바이트가 데이터로 존재할 수 있고, 이 경우 데이터가 텍스트로 표시될 때는 깨진 문자나 공백 문자가 된다. 그리고 이 현상은 후에 이 데이터를 프로그램에서 읽어 처리하는 과정에서 트러블 포인트가 될 수 있다. 


한 예로, xml 전문을 데이터로 가지는 CLOB 컬럼 안에 UTF-8 을 벗어난 바이트가 포함될 경우, 사용자의 눈에는 문제 없는 xml 문서로 보이나, 이 데이터를 가지고 xml validation 을 수행하는 라이브러리나 프로그램을 사용할 경우 에러를 만나게 될 수 있다.



'기술 일반 > 일반' 카테고리의 다른 글

Kotlin] Jackson Json getter setter 사용  (0) 2022.11.24
유니코드와 UTF-8, UTF-16  (0) 2017.04.07

 초기의 아스키는 각 문자를 7비트로 표현하여 총 128개의 문자를 처리할 수 있었다. 그리고 후에 추가적인 문자를 지원해야 할 필요가 있어 여기에 1비트를 추가한 확장 아스키(extended ASCII)가 등장한다. 8비트를 사용하는 이 확장 아스키는 256개의 문자를 표현할 수 있다. 처리해야 할 문자의 종류가 지금처럼 다양하지 않았던 시절, 문자 표현은 이 아스키만으로 충분했다. 오늘날의 글로벌 커뮤니케이션이 예상되지 않았던 시기의 일이다. 이 때는 영어, 아랍어, 중국어 등 다양한 언어가 한 문서에 존재하는 일은 흔치 않았다.


세상에는 수많은 종류의 언어를 사용하는 사람들이 존재하고, 이들 중 라틴계열 문자를 사용하지 않는 사용자는 거의 절반 이상이 될 것이다. 이렇게 다양한 사용자에 대응하는 소프트웨어를 개발하는 데 있어 오직 자신이 사용하는 언어만을 취급한다면 이는 사려깊지 못한 일이다.


이러한 이유로 모든 언어를 수용할 수 있는 문자셋의 필요성이 대두되어 탄생한 것이 유니코드이다. 유니코드는 모든 문자에 대해 각각의 유니크한 번호를 부여하였는데, 이를 코드 포인트라 한다. 유니코드의 이점 중 하나는 처음 256개의 코드는 ISO-8859-1 과 동일하다는 것이다. 고로, 이는 아스키와 동일하다. 그리고 널리 쓰이는 문자들 중 절대 다수의, 엄청나게 많은 문자들이 단 2바이트만으로 표현될 수 있다(Basic Multilingual Plane, BMP). 이제 문자셋에 접근하기 위해 필요한 인코딩에 대해 이야기한다. 여기서는 UTF-8, UTF-16 에 대해 다룰 것이다.



메모리 관련


어떤 종류의 문자가 얼마만큼의 바이트를 필요로 하는가


UTF-8:

1 바이트: 표준 아스키

2 바이트: 아랍, 히브리, 대부분의 유럽계(조지안 문자를 제외한)

3 바이트: BMP

4 바이트: 모든 유니코드 문자


UTF-16:

2 바이트: BMP

4 바이트: 모든 유니코드 문자


여기서 BMP 에 속하지 않은 문자들에는 고대 문자, 수학/음악 기호, 중국/일본/한국 문자 중 몇몇 드물게 보이는 문자들이 포함된다.


만약 대부분의 작업에 아스키 문자를 사용한다면 UTF-8은 메모리에 있어 확실히 더 나은 효율성을 제공한다. 그러나 비 유렵계 문자를 사용한다면, UTF-16 이 UTF-8보다 최대 1.5배 나은 메모리 효율성을 보인다. 이런 사항은 웹페이지나 큰 사이즈의 워드 문서와 같은 거대한 양의 텍스트를 처리할 때 성능에 영향을 줄 수 있다.



인코딩에 있어


UTF-8: 표준 아스키(0-127)와 UTF-8 코드는 완전히 동일하다. 이는 기존 아스키 텍스트의 호환성을 고려해야 할 때 UTF-8이 이상적으로 꼽히는 이유가 된다. 이 밖에 다른 문자들은 2-4 바이트를 필요로 한다. 이 문자들에 대한 처리는 각 바이트의 일부 비트를 예약하는 방법으로 수행되는데, 이는 멀티 바이트 문자의 일부임을 나타내기 위함이다. 특히 아스키 문자와의 충돌을 피하기 위해 각 바이트의 첫 번째 비트는 1이다.


UTF-16: UTF-16의 BMP 문자 표현법은 단순하게도 코드 포인트로 이루어진다. 이에 비해 BMP 외 문자에 대한 표현은 좀 더 복잡한데, 여기에는 surrogate pairs 라 불리는 방법을 이용한다. 유니코드의 2바이트 기본 범위(BMP) 영역을 넘어선 문자들을 Supplementary Characters 라 하는데, surrogate pairs는 이 문자들을 표현하기 위해 도입된 방법이다. surrogate pairs는 두 쌍의 2바이트를 이용하여 2바이트를 넘어서는 문자를 표현한다. 이 2바이트 코드는 BMP 범위에 포함되지만, 유니코드 표준에 의해 BMP 문자가 아님을 보장받는다. 그리고 UTF-16은 기본 단위를 2바이트로 가지기 때문에 엔디안의 영향을 받는다. 이를 보완하기 위해 엔디안을 나타내는 데이터 스트림의 시작부에 예약된 바이트 순서 마크가 위치한다. 고로 엔디안이 지정되어 있지 않은 UTF-16 입력을 읽을 때는 이를 확인할 필요가 있다.


이처럼 UTF-8과 UTF-16은 서로 호환되지 않는다. 입출력을 처리할 때는 어떤 인코딩을 사용하고 있는지 정확히 알 필요가 있다. 



프로그래밍에서의 고려사항


Character 와 String 데이터 타입: 사용하는 프로그래밍 언어에서 어떤 방식으로 인코딩을 하는지가 중요하다. raw 바이트를 비 아스키 문자로 출력하려고 한다면 문제가 발생할 수 있다. 또한 사용하는 문자 타입이 UTF 기반이라고 해도 이것이 알맞는 UTF 문자열을 보장해주지는 않는다. 문자열은 적합하지 않은 바이트 시퀀스를 허용할 수 있다. 일반적으로 이러한 처리에는 UTF를 제대로 지원하는 라이브러리가 사용된다. 어떤 경우든 입출력에 기본 인코딩이 아닌 다른 인코딩을 사용하려 한다면 먼저 컨버팅 작업을 수행하야 할 것이다.


권장되는 인코딩 방식: 어떤 UTF 방식을 사용할지 결정해야 할 경우에는 대부분 작업 환경의 표준을 따르는 것이 최선의 선택이 된다. 예를 들어, 웹 환경에서는 UTF-8이, 다른 자바 환경에서는 UTF-16이 권장된다.


라이브러리 지원: 사용중인 라이브러리가 어떤 인코딩을 지원하는가? 흔치 않는 경우에 발생하는 이슈에 대해서도 커버할 수 있는가? 1-3바이트 문자는 흔히 등장하기 때문에, UTF-8 라이브러리는 대부분 4바이트 문자를 정확하게 지원한다. 그러나 UTF-16을 지원한다고 알려진 모든 라이브러리가 surrogate pairs 를 제대로 지원하지는 않는다. (surrogate pairs가 등장하는 경우는 실제로 매우 드물기 때문이다)


문자 카운팅: 유니코드에는 결합 문자들이 존재한다. 예를 들어 코드 포인트 U+006E (n), U+0303(틸드 부호) 는 ñ을 표현한다. 그런데 코드 포인트 U+00F1 이 표현하는 것도  이다. 이들은 동일한 문자로 보이지만, 단순한 카운팅 알고리즘은 전자에 대해 2를 반환하고, 후자에 대해서는 1를 반환할 것이다. 이것은 잘못된 것이 아니다. 하지만 괜찮은 결과도 아닐 수 있다.


일치성 비교: A, А, Α. 이 A는 모두 같아 보인다. 그러나 앞에서부터 하나는 라틴, 하나는 키릴, 나머지 하나는 그리스 문자이다. 또 이런 경우도 있다. "C, Ⅽ". 하나는 문자이고 다른 하나는 로마 숫자이다. 이것들 외에도 위에서 언급한 결합 문자열 등 여러가지 고려사항이 있다.


Surrogate pairs: 위에서 언급했으므로 패스.

'기술 일반 > 일반' 카테고리의 다른 글

Kotlin] Jackson Json getter setter 사용  (0) 2022.11.24
CLOB 컬럼 핸들링 중 인코딩 문제  (0) 2019.03.08

+ Recent posts