Handling Emojis in JSON and REST APIs

Handling Emojis in JSON and REST APIs

EmojiEmoji
Từ tiếng Nhật (絵文字) có nghĩa là 'ký tự hình ảnh' — các ký hiệu đồ họa nhỏ dùng trong giao tiếp kỹ thuật số để diễn đạt ý tưởng, cảm xúc và sự vật.
are just UnicodeUnicode
Tiêu chuẩn mã hóa ký tự phổ quát gán một số duy nhất cho mỗi ký tự trong tất cả hệ thống chữ viết và bộ ký hiệu, bao gồm cả emoji.
characters, and JSON is specified to support full Unicode. In theory, handling emoji in APIs is straightforward. In practice, bugs creep in through charset misconfiguration, language-specific JSON libraries, database collation settings, and HTTP header restrictions. This guide addresses each layer.

JSON Encoding of Emoji

The JSON specification (RFC 8259) requires JSON to be encoded in UTF-8UTF-8
Kiểu mã hóa Unicode có chiều rộng thay đổi, dùng từ 1 đến 4 byte cho mỗi ký tự, thống trị trên web (98%+ website sử dụng).
, UTF-16UTF-16
Kiểu mã hóa Unicode có chiều rộng thay đổi, dùng 2 hoặc 4 byte cho mỗi ký tự, được JavaScript, Java và Windows dùng nội bộ.
, or UTF-32UTF-32
Kiểu mã hóa Unicode có chiều rộng cố định, dùng đúng 4 byte cho mỗi ký tự, cho phép ánh xạ trực tiếp điểm mã nhưng tốn nhiều bộ nhớ hơn.
, with UTF-8 as the default and recommended encoding. Emoji characters are valid JSON string content.

The simplest and most interoperable approach is to include emoji characters directly in the JSON string:

{
  "message": "Hello 👋",
  "reactions": ["❤️", "😂", "🚀"],
  "status": "🟢 operational"
}

Most modern JSON parsers handle this correctly with no configuration.

Unicode Escape Sequences

An alternative is to escape emoji as \uXXXX sequences. For characters above U+FFFF (most modern emoji), this requires a surrogate pairSurrogate Pair
Hai đơn vị mã UTF-16 (một surrogate cao U+D800-U+DBFF theo sau là một surrogate thấp U+DC00-U+DFFF) cùng nhau đại diện cho một ký tự trên U+FFFF.
:

{
  "message": "Hello \uD83D\uDC4B"
}

Here, \uD83D\uDC4B is the surrogate pair for 👋 (U+1F44B). Both forms are valid JSON. However:

  • The native UTF-8 form ("👋") is shorter and more readable
  • The \u escape form is necessary only if the transport layer cannot safely pass non-ASCII bytes
  • Some older JSON libraries produce the \u form by default

Python: Controlling Escape Behavior

import json

data = {"emoji": "👋🌍🚀", "message": "Hello 世界"}

# Default: ensures_ascii=True — escapes all non-ASCII as \uXXXX
print(json.dumps(data))
# {"emoji": "\ud83d\udc4b\ud83c\udf0d\ud83d\ude80", "message": "Hello \u4e16\u754c"}

# ensure_ascii=False — outputs UTF-8 characters directly (recommended)
print(json.dumps(data, ensure_ascii=False))
# {"emoji": "👋🌍🚀", "message": "Hello 世界"}

# With indent for readability
print(json.dumps(data, ensure_ascii=False, indent=2))

Always use ensure_ascii=False for APIs unless you have a specific reason to escape non-ASCII.

Django REST Framework

# settings.py
# DRF uses ensure_ascii=False by default since version 3.x
# But verify your renderer configuration:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
}

# JSONRenderer sets unicode_to_ascii=False by default
# Content-Type: application/json; charset=utf-8 is set automatically

Node.js / JavaScript

const data = { emoji: "👋🌍🚀", message: "Hello" };

// JSON.stringify always outputs native UTF-8 characters in modern engines
const json = JSON.stringify(data);
console.log(json); // {"emoji":"👋🌍🚀","message":"Hello"}

// Parsing is equally straightforward
const parsed = JSON.parse('{"emoji":"👋"}');
console.log(parsed.emoji); // 👋
console.log(parsed.emoji.codePointAt(0).toString(16)); // 1f44b

Go

package main

import (
    "encoding/json"
    "fmt"
)

type Response struct {
    Emoji   string `json:"emoji"`
    Message string `json:"message"`
}

func main() {
    data := Response{Emoji: "👋🌍🚀", Message: "Hello"}

    // Default: encodes emoji as \u escapes + surrogate pairs
    b, _ := json.Marshal(data)
    fmt.Println(string(b))
    // {"emoji":"\ud83d\udc4b\ud83c\udf0d\ud83d\ude80","message":"Hello"}

    // Use json.Encoder with SetEscapeHTML(false) AND a custom encoder
    // For native UTF-8, use json.RawMessage or a custom marshaler
    // Alternatively, use the sonic or go-json library
}

Go's encoding/json package escapes non-ASCII by default. For APIs returning emoji, use json.NewEncoder with SetEscapeHTML(false) or a third-party library like github.com/bytedance/sonic:

import "github.com/bytedance/sonic"

b, _ := sonic.Marshal(data)
fmt.Println(string(b))
// {"emoji":"👋🌍🚀","message":"Hello"}

HTTP Content-Type Headers

Always declare UTF-8 encoding in your Content-Type header:

Content-Type: application/json; charset=utf-8

Without the charset declaration, some clients may default to Latin-1 or ASCII and misinterpret the emoji bytes. In Python's Flask:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/status')
def status():
    # Flask's jsonify uses ensure_ascii=False since Flask 1.0
    # and sets Content-Type: application/json automatically
    return jsonify({"status": "ok", "emoji": "🟢"})

Emoji in HTTP Headers

HTTP/1.1 headers (RFC 7230) are restricted to printable ASCII characters (0x20–0x7E). You cannot place raw emoji in HTTP headers.

If you need to pass emoji-containing values in headers (e.g., user display names in custom headers), you must encode them:

import base64

# Option 1: Base64 encode
display_name = "Alex 👋"
header_value = base64.b64encode(display_name.encode('utf-8')).decode('ascii')
# X-Display-Name: QWxleCD8n5SL

# Option 2: Percent-encode
from urllib.parse import quote
encoded = quote(display_name)
# X-Display-Name: Alex%20%F0%9F%91%8B

# Option 3: Use RFC 5987 encoding for structured headers
# Content-Disposition: attachment; filename*=UTF-8''%F0%9F%91%8B.txt

Sorting and Filtering Emoji in APIs

When your API returns emoji-containing strings and supports sorting, be aware that Unicode code point order is not always user-intuitive:

items = ["🍎 Apple", "🍌 Banana", "🍒 Cherry"]
# Sorting by code point order works correctly here since emoji differ
items.sort()  # OK for simple cases

# For locale-aware sorting, use PyICU or icu4c
import icuICU (ICU)
International Components for Unicode — thư viện mã nguồn mở được sử dụng rộng rãi, cung cấp hỗ trợ Unicode và quốc tế hóa, bao gồm xử lý emoji.
collator = icu.Collator.createInstance(icu.Locale.getDefault()) items.sort(key=collator.getSortKey)

Filtering Emoji in Request Bodies

You may want to sanitize or restrict emoji in API inputs. Approach depends on requirements:

import regex

EMOJI_PATTERN = regex.compile(r'\p{Extended_Pictographic}', regex.UNICODE)

def strip_emoji(text: str) -> str:
    """Remove all emoji from text."""
    return EMOJI_PATTERN.sub('', text)

def validate_no_emoji(text: str) -> bool:
    """Return True if text contains no emoji."""
    return not EMOJI_PATTERN.search(text)

def count_emoji(text: str) -> int:
    return len(EMOJI_PATTERN.findall(text))

# In a FastAPI endpoint
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class CreatePost(BaseModel):
    content: str

@app.post("/posts")
def create_post(body: CreatePost):
    if count_emoji(body.content) > 10:
        raise HTTPException(400, "Post cannot contain more than 10 emoji")
    return {"content": body.content}

GraphQL and Emoji

GraphQL uses UTF-8 JSON for transport, so emoji in GraphQL string fields work without special treatment:

type Post {
  content: String!
  reactions: [String!]!
}
mutation {
  createPost(content: "Launched 🚀", reactions: ["❤️", "🔥"]) {
    id
    content
  }
}

The GraphQL spec defines strings as a sequence of Unicode scalar values, which includes all emoji (scalar values exclude surrogate code points U+D800–U+DFFF).

Common Pitfalls Summary

Pitfall Cause Fix
Emoji appear as \ud83d\udc4b ensure_ascii=True in Python Use ensure_ascii=False
Mojibake (ð¾) Content-Type missing charset Add ; charset=utf-8
Emoji truncated in DB utf8 instead of utf8mb4 in MySQL Use utf8mb4 collation
Emoji in custom headers rejected HTTP header ASCII restriction Percent-encode or Base64
String length mismatch Measuring bytes vsVariation Selector (VS)
Các ký tự Unicode (VS-15 U+FE0E và VS-16 U+FE0F) xác định xem một ký tự được hiển thị dưới dạng văn bản (đơn sắc) hay emoji (có màu).
characters
Use grapheme cluster count

Explore More on EmojiFYI

Công cụ liên quan

🔀 So sánh nền tảng So sánh nền tảng
So sánh cách emoji hiển thị trên Apple, Google, Samsung, Microsoft và nhiều hơn nữa. Xem sự khác biệt trực quan cạnh nhau.
🔍 Trình phân tích chuỗi Trình phân tích chuỗi
Giải mã chuỗi ZWJ, modifier tông màu da, chuỗi phím và cặp cờ thành các thành phần riêng lẻ.

Thuật ngữ

Cụm grapheme Cụm grapheme
Một ký tự mà người dùng nhìn thấy như một đơn vị, có thể được tạo thành từ nhiều điểm mã Unicode hiển thị cùng nhau như một đơn vị …
Điểm mã Điểm mã
Giá trị số duy nhất được gán cho mỗi ký tự trong tiêu chuẩn Unicode, được viết theo định dạng U+XXXX (ví dụ: U+1F600 cho 😀).
Emoji Emoji
Từ tiếng Nhật (絵文字) có nghĩa là 'ký tự hình ảnh' — các ký hiệu đồ họa nhỏ dùng trong giao tiếp kỹ thuật số để diễn đạt ý tưởng, …
ICU (ICU) ICU (ICU)
International Components for Unicode — thư viện mã nguồn mở được sử dụng rộng rãi, cung cấp hỗ trợ Unicode và quốc tế hóa, bao gồm xử lý emoji.
Surrogate Pair Surrogate Pair
Hai đơn vị mã UTF-16 (một surrogate cao U+D800-U+DBFF theo sau là một surrogate thấp U+DC00-U+DFFF) cùng nhau đại diện cho một ký tự trên U+FFFF.
Unicode Unicode
Tiêu chuẩn mã hóa ký tự phổ quát gán một số duy nhất cho mỗi ký tự trong tất cả hệ thống chữ viết và bộ ký hiệu, bao gồm …
UTF-16 UTF-16
Kiểu mã hóa Unicode có chiều rộng thay đổi, dùng 2 hoặc 4 byte cho mỗi ký tự, được JavaScript, Java và Windows dùng nội bộ.
UTF-32 UTF-32
Kiểu mã hóa Unicode có chiều rộng cố định, dùng đúng 4 byte cho mỗi ký tự, cho phép ánh xạ trực tiếp điểm mã nhưng tốn nhiều bộ nhớ …
UTF-8 UTF-8
Kiểu mã hóa Unicode có chiều rộng thay đổi, dùng từ 1 đến 4 byte cho mỗi ký tự, thống trị trên web (98%+ website sử dụng).

Emoji liên quan

Bài viết liên quan