개발 한 스푼

Emoji 저장 실패 오류

idleday 2023. 4. 14. 12:02

Emoji 저장 실패 오류

 

에러 현상

Incorrect string value: '\xF0\x9F\x93\xB201...' for column 'content'

 

에러 이유

  • 윈도우 이모지(utf8mb4)와 DB(utf8)의 문자 인코딩 설정 상이
  • MySQL DB collation
    • MariaDB에서 table을 생성할 때 charset과 collation을 지정한다.
    • charset은 문자(기호)와 인코딩의 집합이고,collation은 charset안의 문자들의 비교를 위한 규칙들이다(참고).
    • 보통 한국어가 포함되는 테이블의 charset은 utf8, collation은 utf8_general_ci를 사용한다.
  • utf8_general_ci는 3byte만 허용
    • utf8은 유니코드 문자 인코딩 방식 중 하나이다.
    • utf8은 4byte까지 사용하나 전세계 언어를 표현하는데에는 3byte면 충분하다.
    • 그래서 Mysql 및 MariaDB에는 효율성을 위해 utf8_general_ci를 3byte로 설계했다.
    • 그러나 emoji 중 4byte짜리가 등장하면서utf8_general_ci에 저장하지 못하는 문제가 발생한 것이다.

 

해결방법

해결방법 1: collation 변경

 

해결방법 2: 4byte emoji 필터링

  • 참고
  • 테스트
    str.replace(new RegExp('[\\u00FF-\\uFFFF]+','g'), '');
    
    function removeEmojis (string) {
      var regex = /(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|\\ud83c[\\udd70-\\udd71]|\\ud83c[\\udd7e-\\udd7f]|\\ud83c\\udd8e|\\ud83c[\\udd91-\\udd9a]|\\ud83c[\\udde6-\\uddff]|\\ud83c[\\ude01-\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|\\ud83c[\\ude32-\\ude3a]|\\ud83c[\\ude50-\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff])/g;
      return string.replace(regex, '');
    }
    
    var sms = "이모지 테스트 💲"
    
    //Doesn't catch some emojis
    //sms = sms.replace(/([#0-9]\\u20E3)|[\\xA9\\xAE\\u203C\\u2047-\\u2049\\u2122\\u2139\\u3030\\u303D\\u3297\\u3299][\\uFE00-\\uFEFF]?|[\\u2190-\\u21FF][\\uFE00-\\uFEFF]?|[\\u2300-\\u23FF][\\uFE00-\\uFEFF]?|[\\u2460-\\u24FF][\\uFE00-\\uFEFF]?|[\\u25A0-\\u25FF][\\uFE00-\\uFEFF]?|[\\u2600-\\u27BF][\\uFE00-\\uFEFF]?|[\\u2900-\\u297F][\\uFE00-\\uFEFF]?|[\\u2B00-\\u2BF0][\\uFE00-\\uFEFF]?|(?:\\uD83C[\\uDC00-\\uDFFF]|\\uD83D[\\uDC00-\\uDEFF])[\\uFE00-\\uFEFF]?/g, '');	
    
    //Seems to catch all emojis but does removes character before if no space and adds/replaces characters
    // sms = sms.replace(/[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|\\ud83c[\\udd70-\\udd71]|\\ud83c[\\udd7e-\\udd7f]|\\ud83c\\udd8e|\\ud83c[\\udd91-\\udd9a]|\\ud83c[\\udde6-\\uddff]|[\\ud83c[\\ude01-\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|[\\ud83c[\\ude32-\\ude3a]|[\\ud83c[\\ude50-\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff]?/g, '');
    
    sms = sms.replace(/(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\u0023-\\u0039]\\ufe0f?|\\ud83c[\\udd70-\\udd71]|\\ud83c[\\udd7e-\\udd7f]|\\ud83c\\udd8e|\\ud83c[\\udd91-\\udd9a]|\\ud83c[\\udde6-\\uddff]|\\ud83c[\\ude01-\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|\\ud83c[\\ude32-\\ude3a]|\\ud83c[\\ude50-\\ude51]|\\ud83c\\udc04|\\ud83c\\udccf)?/g, '');
    
    console.log(sms);
    

 

해결방법 3: 4byte emoji를 대체 텍스트로 치환

  • 4byte emoji의 16진수 값 확인
    • Ref1. Emoji 에서 (1F004~1F9E6).
    • Ref2. Emoji Smileys 는 전부 해당. (1F600~1F9D0)
  • Java의 emoji regex pattern
    • "[\\ud83c\\udc00-\\ud83c\\udfff]|[\\ud83d\\udc00-\\ud83d\\udfff]|[\\u2600-\\u27ff]"
  • Golang에서 정규식(Regular expression)으로 emoji 처리하기
    • utf8 인코딩은 16진수로 표현한다. ex. U+0800
    • 정규식에서의 표현은 \u0800 와 같이 표현할 수 있는데 Golang에서는 이 표현 대신 \x{0800}으로 표현한다(참고)
    • 4byte emoji를 대체 텍스트로 치환하는 코드
        package main
      
        import (
            "fmt"
            "regexp"
        )
      
        func main() {
            var emojiRx = regexp.MustCompile(`[\\x{1F004}-\\x{1F9E6}]|[\\x{1F600}-\\x{1F9D0}]`)
            var s = emojiRx.ReplaceAllString("Thats a nice joke 🥰 🥵 🥶 🥳 🥴 🥺 👨‍🦰 👩‍🦰 👨‍🦱 👩‍🦱 👨‍🦲 👩‍🦲 👨‍🦳 👩‍🦳 🎨 🎦", `[e]`)
            fmt.Println(s)
        }
      
  • emoji 웹 적용법
  •  
  • 함수를 이용한 문자열 치환
    var text = 'aaaabbbbcccc';
    
    function replaceWithAddingBrace(match) {
      return '{' + match + '}';
    }
    
    text.replace(/b/gi, replaceWithAddingBrace);
    // aaaa{b}{b}{b}{b}cccc
    
    위 코드는 replace 함수에 문자열 내의 b를 찾아서 {b}형태로 변경해주는 함수를 전달하여 조금 더 복잡한 형태의 치환을 하는 예제 입니다.
  • replace 함수에서는 조금 더 복잡한 형태의 치환을 할 수 있도록 하기위해 파라미터로 함수를 받아서 문자열 치환을 할수 있도록 제공하고 있습니다.
  • 문자를 unicode로 변환 str.charCodeAt(0).toString(16)
  • charToUnicode = function(str) { if (!str) return false; // Escaping if not exist var unicode = ''; for (var i = 0, l = str.length; i < l; i++) { **unicode += '\\\\' + str[i].charCodeAt(0).toString(16)**; }; return unicode; }
  • unicode를 문자로 변환 str.fromCharCode()
  • String.fromCharCode(parseInt(unicode,16))
  • 유니코드 정규표현식 (regular expressions - unicode) Unicode Property
  • const str = 'a❤가✔あ😀ア🍖0'; [**'Emoji', 'Emoji_Presentation'**].forEach(function(binaryUnicodeProperty){ const pattern = new RegExp(`\\\\\\p{${binaryUnicodeProperty}}`, 'gu'); const matches = str.match(pattern); console.log(`[${binaryUnicodeProperty}]`); console.log(matches); });