JMESPath cho người mới: truy vấn projection vượt JSONPath
Kết luận: JMESPath định hình lại lô object trực tiếp hơn JSONPath. Hướng dẫn cho người mới này bao gồm cú pháp, projection, pipe, hàm tích hợp và cách dùng trong AWS CLI.

Tóm lại: dùng JSON Pointer để định vị theo vị trí, JSONPath để lọc hoặc đệ quy, và JMESPath khi cần định hình lại kết quả hoặc chiếu nó trên dòng lệnh; một trường đã biết thì không cần cái nào.
Nếu mục tiêu của bạn là định hình lại một lô object thành hình dạng khác, JMESPath thường trực tiếp hơn JSONPath. Nó không tìm nút; nó dựng ra một kết quả.
1. JMESPath là gì
JMESPath là một ngôn ngữ truy vấn được thiết kế riêng cho JSON. Ban đầu nó được tạo để lọc đầu ra AWS CLI và sau đó lan sang nhiều ngôn ngữ qua các triển khai như jmespath.js, jmespath.py và go-jmespath. Mục tiêu của nó khác JSONPath:
- JSONPath tập trung vào việc chọn một tập nút theo đường dẫn và điều kiện, và trả về một tập nút;
- JMESPath tập trung vào việc biến đổi JSON đầu vào và trả về một giá trị với hình dạng bất kỳ.
Khác biệt này khiến JMESPath đặc biệt giỏi ở projection danh sách, đổi tên trường và cắt tỉa kết quả, ví dụ thu hai mươi trường từ một phản hồi API về ba trường mà frontend thật sự cần. Nếu bạn chưa đọc phần cơ bản của JSONPath, hãy bắt đầu với Ngôn ngữ truy vấn JSONPath trong thực tế.
2. Cú pháp cơ bản: từ định danh đến chỉ số
JMESPath dùng ít ký hiệu hơn JSONPath và đọc giống một biểu thức trích xuất hơn:
| Cú pháp | Ý nghĩa | Ví dụ |
|---|---|---|
name |
Truy cập một member của object | people |
a.b |
Truy cập member lồng nhau | store.bicycle.color |
[0] |
Truy cập chỉ số mảng | people[0] |
[-1] |
Truy cập phần tử cuối | people[-1] |
[0:2] |
Slice, bao gồm điểm đầu và loại trừ điểm cuối | people[0:2] |
[*] |
Projection danh sách | people[*].name |
[] |
Projection làm phẳng | people[].tags[] |
[?condition] |
Projection lọc | people[?age > 18] |
{key: expr} |
Hash chọn nhiều | {name: name, age: age} |
[expr, expr] |
Danh sách chọn nhiều | [name, age] |
| |
Đưa kết quả trái vào phía phải | people[*].age | [0] |
Một ví dụ tối thiểu:
{
"people": [
{ "name": "Alice", "age": 31, "city": "Shanghai" },
{ "name": "Bob", "age": 17, "city": "Beijing" }
]
}
people[0].name → "Alice"
people[-1].city → "Beijing"
people[0:2].name → ["Alice", "Bob"]
Hãy để ý vì sao people[0:2].name trả về một mảng: một slice trả về danh sách, và .name theo sau được tự động chiếu lên mọi phần tử trong danh sách đó. Đây là khác biệt lớn nhất giữa JMESPath và truy cập object thông thường.
3. Projection: biến một danh sách thành danh sách khác
Projection là khái niệm cốt lõi trong JMESPath. Nó hoạt động theo hai bước: đánh giá mọi phần tử của một danh sách, rồi thu kết quả vào một danh sách mới. Những phần tử đánh giá thành null bị bỏ tự động.
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // a list of objects
people[?age > 18].name // ["Alice"]
Dòng thứ hai là một biểu thức projection điển hình: people[*].{name: name, age: age}. Nó dựng lại mọi phần tử thành { "name": ..., "age": ... } và bỏ trường city thừa. So với JSONPath, vốn cho bạn các nút rồi bạn map trong mã, cách này tiết kiệm một vòng lặp.
Hai loại projection đáng phân biệt:
[*]giữ cấu trúc danh sách gốc và bỏ qua giá trị null;[]là projection làm phẳng, trước tiên gộp một cấp danh sách lồng nhau, phù hợp với biểu thức nhưpeople[].tags[]cần "mọi tag của mọi người".
4. Pipe và chọn nhiều
Pipe | nghĩa là "dừng projection hiện tại và coi kết quả là đầu vào mới". Cách dùng phổ biến nhất là lấy một chỉ số sau một projection:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → the maximum age
Không có pipe, people[*].name[0] được hiểu là lấy ký tự đầu của mỗi tên, không phải điều bạn muốn. Pipe là ký hiệu dễ bị bỏ qua nhất và hữu ích nhất trong JMESPath.
Có hai dạng chọn nhiều:
// Multi-select hash: the result is an object
people[0].{name: name, age: age}
// Multi-select list: the result is an array
people[0].[name, age]
Hash chọn nhiều tốt để dựng kết quả có tên, còn danh sách chọn nhiều tốt để tiêu thụ theo vị trí. Cả hai đều có thể lồng trong projection, nhưng lưu ý: trong hash chọn nhiều, một khoá có giá trị null vẫn được giữ, trong khi projection bỏ toàn bộ phần tử null.
5. Hàm tích hợp
Đặc tả JMESPath định nghĩa một tập hàm. Những hàm phổ biến gồm:
| Hàm | Mục đích | Ví dụ |
|---|---|---|
length() |
Lấy độ dài | length(people) |
sort() |
Sắp xếp giá trị | sort(people[*].age) |
sort_by() |
Sắp xếp theo một trường | sort_by(people, &age) |
max() / min() |
Lớn nhất / nhỏ nhất | max(people[*].age) |
sum() |
Tính tổng | sum(people[*].age) |
keys() |
Lấy khoá object | keys(people[0]) |
contains() |
Kiểm tra chứa | contains(name, "Ali") |
to_string() |
Chuyển thành chuỗi | to_string(age) |
Tham số thứ hai của sort_by là một biểu thức &, nghĩa là "lấy trường này từ mỗi phần tử":
sort_by(people, &age)[*].name
Những hàm này cho phép JMESPath thực hiện tổng hợp đơn giản, trong khi JSONPath chỉ được thiết kế để chọn; việc tính tổng và nhóm thường cần jq hoặc mã.
6. So sánh song song với JSONPath
| Khía cạnh | JSONPath | JMESPath |
|---|---|---|
| Mục tiêu chính | Chọn nút | Biến đổi dữ liệu |
| Ký hiệu gốc | $ |
Không có; viết thẳng tên trường |
| Nút hiện tại | @ |
@ (chỉ trong tham số hàm) |
| Filter | [?(@.age > 18)] |
[?age > 18] |
| Wildcard | [*] |
[*] hoặc [] |
| Dựng object mới | Không hỗ trợ | {name: name} |
| Pipe | Không hỗ trợ | | |
| Chuẩn | RFC 9535 | Đặc tả JMESPath |
Hướng dẫn quyết định trong một câu:
- Dùng JSONPath khi bạn chỉ cần tìm các nút khớp một điều kiện;
- Dùng JMESPath khi cần định hình lại kết quả thành thứ frontend mong đợi;
- Dùng cả hai đều không khi bạn đang đọc một trường đã biết; hãy truy cập thuộc tính trực tiếp.
Nếu kết quả truy vấn phải được ghi lại vào tài liệu, cả hai ngôn ngữ đều không phù hợp. Hãy dùng JSON Pointer và patch; xem JSON Pointer, Patch và Merge Patch.
7. JMESPath trong AWS CLI
Tham số --query của AWS CLI chính là JMESPath, và đây là trường hợp dùng rộng nhất của nó:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
Lệnh này làm ba việc: nó làm phẳng hai danh sách lồng nhau bằng [], chỉ giữ ba trường bằng một hash chọn nhiều, và hiển thị kết quả thành bảng bằng --output table. Với JSONPath bạn sẽ cần nhiều cấp hơn và không có hiệu ứng "đổi tên cộng cắt tỉa".
Các dạng phổ biến khác:
# Only running instances
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,PublicIpAddress]'
# The value of the Name tag
aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==`Name`].Value[]'
Lưu ý rằng dấu backtick bao literal, không phải chuỗi. Trong AWS CLI, chuỗi dùng nháy đơn còn literal JSON dùng backtick. Khác biệt này là một trong những lỗi phổ biến nhất của người mới.
8. Những lỗi thường gặp
- Trộn
[*]với[]: cái trước giữ lồng, cái sau làm phẳng một cấp, và cấu trúc kết quả hoàn toàn khác; - Quên pipe:
people[*].name[0]lấy ký tự đầu của mỗi tên, không phải tên đầu tiên; - Viết
@trong filter: filter của JMESPath dùng thẳng tên trường, nên[?@.age > 18]là sai; - Coi
nullnhư chuỗi rỗng: projection bỏnull, nhưng hash chọn nhiều vẫn giữ khoá; - Dùng nháy kép quanh literal trong AWS CLI: shell mở rộng chúng trước và viết lại biểu thức.
Lời khuyên gỡ lỗi: kiểm chứng biểu thức trên đầu vào nhỏ nhất có thể trước khi áp dụng lên dữ liệu thật, và thêm --output json trong AWS CLI để xem kết quả thô. Với việc chỉnh sửa và định dạng JSON hằng ngày, kết hợp với Trình soạn thảo JSON và công cụ dòng lệnh giúp định vị đường dẫn trường nhanh hơn nhiều.
FAQ
Có thể dùng JMESPath và JSONPath cùng nhau không?
Có, nhưng thường không cần thiết. Cách phổ biến là cắt tỉa một object nhỏ bằng JMESPath, rồi truy cập thuộc tính thông thường trong mã. Nếu thật sự cần định vị theo đường dẫn rồi sửa, JSONPath đọc còn JSON Pointer ghi, đó là phân công rõ ràng hơn.
Vì sao people[*].name trả về thứ khác một chuỗi đơn?
Vì [*] là một projection, và kết quả luôn là một danh sách, kể cả khi chỉ có một người. Để lấy một giá trị đơn, hãy thêm pipe và một chỉ số: people[*].name | [0].
Điều gì xảy ra với null bên trong một projection?
Projection danh sách tự động bỏ qua những phần tử đánh giá thành null, nên kết quả có thể ngắn hơn danh sách gốc. Hash chọn nhiều giữ khoá và bảo toàn giá trị null, điều này quan trọng khi dựng phản hồi API.
JMESPath có làm được tổng hợp và nhóm không?
Nó làm được tổng hợp cơ bản với sum(), max() và sort_by(). Việc nhóm, join và hàm cửa sổ nằm ngoài chuẩn. Hãy để jq, SQL hoặc mã xử lý phân tích phức tạp.
Làm sao quyết định giữa JMESPath và JSONPath?
Dùng JSONPath khi cần chọn nút theo đường dẫn và điều kiện và nhận một tập nút; dùng JMESPath khi cần định hình lại kết quả thành thứ frontend mong đợi, với đổi tên trường và cắt tỉa. Khi đang đọc một trường đã biết, hãy dùng cả hai đều không; chỉ truy cập thuộc tính.
Có nên dùng pipe để lấy giá trị đầu tiên của projection không?
Có. people[*].name là một projection, nên kết quả luôn là một danh sách; không có pipe, people[*].name[0] lấy ký tự đầu của mọi tên. Hãy viết people[*].name | [0] để lấy giá trị đầu tiên.