Sinh struct Go từ JSON: quy ước tên viết hoa, thẻ json, và chỗ bắt buộc phải dùng con trỏ
Dán một khối JSON, công cụ sinh struct Go với tên trường theo đúng quy ước của cộng đồng Go, thẻ json giữ nguyên tên khóa gốc, tùy chọn thêm omitempty và con trỏ cho trường có thể rỗng. Bạn chọn tách struct lồng thành kiểu riêng hay để nội tuyến, và bật tắt việc nhận diện chuỗi thời gian thành time.Time.
Tính năng nổi bật
- Tên trường viết hoa chữ đầu và viết hoa toàn bộ các chữ viết tắt quen thuộc như ID, URL, API, HTTP, UUID
- Thẻ json giữ nguyên tên khóa gốc kể cả khi khóa có gạch dưới, gạch nối hay dấu cách
- Công tắc thêm omitempty cho những trường công cụ nhận ra là có thể rỗng
- Công tắc dùng con trỏ cho trường có thể rỗng, để phân biệt không có với giá trị mặc định
- Chọn tách struct lồng thành kiểu riêng có tên, hoặc để struct ẩn danh nội tuyến ngay trong trường
- Gộp mọi phần tử của mảng đối tượng thành một hình dạng chung và đánh dấu khóa không có ở mọi phần tử
- Nhận diện chuỗi thời gian chuẩn thành time.Time và tự thêm dòng nhập gói thời gian
- Ô đặt tên struct gốc và ô đặt tên gói, kèm bộ đếm số struct sinh ra
Vì sao gõ tay struct cho một phản hồi lại sinh ra lỗi im lặng trong Go
Gói giải mã JSON trong thư viện chuẩn của Go không báo lỗi khi tên trường không khớp. Nó duyệt các khóa trong JSON, tìm trường tương ứng trong struct, và nếu không tìm thấy thì bỏ qua khóa đó rồi tiếp tục. Kết quả là chương trình chạy trơn tru, hàm giải mã trả về lỗi rỗng, còn trường của bạn vẫn mang giá trị mặc định. Với chuỗi thì đó là chuỗi trống, với số là không, với đúng sai là sai, và không có gì trong nhật ký báo cho bạn biết. Người ta thường chỉ phát hiện khi thấy báo cáo thiếu số liệu sau vài ngày. Nguyên nhân gần như luôn là một lỗi gõ nhỏ trong thẻ json, hoặc quên hẳn thẻ khi tên khóa có gạch dưới. Sinh struct thẳng từ dữ liệu thật loại bỏ hoàn toàn nhóm lỗi này vì tên khóa được chép nguyên văn vào thẻ chứ không đi qua tay bạn.
Lợi ích khi sử dụng
- Tên khóa vào thẻ json nguyên văn nên không còn lỗi giải mã im lặng vì gõ sai
- Tên trường ra đúng quy ước cộng đồng Go, không bị công cụ kiểm tra mã nguồn nhắc lại
- Hiểu ngay trường nào cần con trỏ nhờ chú thích công cụ chèn sẵn ở cuối dòng
- Mảng đối tượng được gộp thành một hình dạng chung nên không bỏ sót trường chỉ có ở vài phần tử
- Dữ liệu phản hồi không rời khỏi trình duyệt nên dùng được với giao diện lập trình nội bộ
Cách sinh struct Go từ một phản hồi
- 1Chép nguyên phần thân phản hồi thật từ tab mạng của trình duyệt hoặc từ lệnh gọi dòng lệnh, rồi dán vào ô bên trái.
- 2Đặt tên struct gốc theo tên tài nguyên và đặt tên gói cho khớp với thư mục bạn sẽ đặt tệp vào.
- 3Cân nhắc ba công tắc: omitempty ảnh hưởng tới lúc mã hóa ra, con trỏ ảnh hưởng tới lúc giải mã vào, còn lồng nội tuyến chỉ ảnh hưởng tới cách trình bày.
- 4Bấm sinh struct, đọc từ trên xuống vì struct gốc luôn nằm đầu và các struct con xếp theo thứ tự phát hiện.
- 5Rà lại ba chỗ: trường mang giá trị rỗng trong mẫu, trường số tiền, và tên struct con nếu bạn muốn dùng chung ở nhiều nhánh.
Chữ hoa ở đầu tên trường không phải chuyện thẩm mỹ
Trong Go, chữ cái đầu của một tên quyết định phạm vi truy cập: viết hoa là xuất ra ngoài gói, viết thường là chỉ dùng trong gói. Gói giải mã JSON của thư viện chuẩn làm việc thông qua cơ chế phản chiếu, và cơ chế đó không đọc được các trường không xuất. Vì vậy một trường viết thường sẽ không bao giờ nhận được dữ liệu, kể cả khi bạn gắn thẻ json đúng tên khóa. Đây là lỗi kinh điển của người mới, và nó im lặng hoàn toàn. Do đó mọi trường trong struct dùng để giải mã đều phải viết hoa chữ đầu, còn tên khóa thật nằm trong thẻ. Về cách viết hoa, cộng đồng Go có quy ước riêng khác hầu hết ngôn ngữ khác: các chữ viết tắt phải viết hoa toàn bộ chứ không viết hoa mỗi chữ đầu. Đúng là UserID chứ không phải UserId, đúng là APIURL chứ không phải ApiUrl, đúng là HTTPStatus chứ không phải HttpStatus. Công cụ này áp dụng danh sách viết tắt quen thuộc gồm ID, URL, URI, API, HTTP, HTTPS, JSON, XML, HTML, SQL, DB, UUID, TLS, IP, TCP, UDP và một số nữa. Còn một điểm ít người biết: khi giải mã, gói chuẩn so khớp tên không phân biệt hoa thường, nên trường tên Name vẫn nhận được khóa name dù không có thẻ. Đừng dựa vào điều đó, vì lúc mã hóa ngược ra thì tên khóa sẽ là Name viết hoa, và bên nhận sẽ không hiểu.
omitempty kiểm tra giá trị mặc định chứ không kiểm tra sự vắng mặt
Đây là hiểu nhầm gây thiệt hại nhiều nhất khi làm việc với thẻ json trong Go. Từ omitempty nghe như là bỏ qua nếu trường không được đặt, nhưng thực tế nó bỏ qua nếu giá trị bằng giá trị mặc định của kiểu. Với kiểu đúng sai, giá trị mặc định là sai. Nghĩa là một trường tên là đã kích hoạt mang giá trị sai sẽ hoàn toàn biến mất khỏi JSON gửi đi. Bên nhận không thấy khóa đó, và nếu bên nhận có logic mặc định riêng thì kết quả có thể ngược hẳn ý bạn. Tương tự, với kiểu số thì giá trị không bị bỏ, nên một trường số dư bằng không hay số lượng bằng không sẽ mất tích, và trường chuỗi rỗng cũng vậy. Với lát cắt và bản đồ thì cả trường hợp rỗng lẫn trường hợp không có phần tử nào đều bị bỏ. Hệ quả thực tế: đừng bao giờ đặt omitempty lên trường đúng sai hoặc trường số mà giá trị không là hợp lệ, trừ khi bạn đã bàn với bên nhận. Cách xử lý đúng khi bạn thật sự cần phân biệt không đặt với đặt bằng giá trị mặc định là dùng con trỏ kèm omitempty: khi con trỏ rỗng thì trường biến mất, khi con trỏ trỏ tới giá trị sai thì trường vẫn xuất hiện với giá trị sai. Cuối cùng, omitempty chỉ có tác dụng lúc mã hóa ra, nó không ảnh hưởng gì tới lúc giải mã vào.
Con trỏ là cách duy nhất phân biệt không có với bằng không
Go không có kiểu tùy chọn dựng sẵn, và mọi biến luôn mang một giá trị mặc định ngay khi khai báo. Điều đó tạo ra một điểm mù: sau khi giải mã, nhìn vào một trường số bằng không bạn không có cách nào biết đó là do máy chủ trả về số không, do máy chủ trả về giá trị rỗng, hay do khóa đó không có mặt trong phản hồi. Ba tình huống này thường mang ý nghĩa khác hẳn nhau trong nghiệp vụ. Con trỏ giải quyết bằng cách thêm một trạng thái thứ tư là con trỏ rỗng: nếu khóa vắng mặt hoặc mang giá trị rỗng, con trỏ vẫn rỗng; nếu khóa có giá trị, con trỏ trỏ tới giá trị đó kể cả khi giá trị ấy bằng không. Đó là lý do công tắc dùng con trỏ trong công cụ này bật sẵn cho những trường mà mẫu dữ liệu cho thấy có thể rỗng hoặc không có mặt ở mọi phần tử của mảng. Cái giá phải trả là mã đọc dữ liệu dài hơn và nguy hiểm hơn: mỗi lần lấy giá trị ra bạn phải kiểm tra con trỏ khác rỗng trước, quên là chương trình dừng ngay lúc chạy. Vì vậy quy tắc thực dụng là chỉ dùng con trỏ ở đúng những trường mà sự khác nhau giữa không có và bằng không thật sự ảnh hưởng tới quyết định của chương trình. Với các trường còn lại, giá trị mặc định là đủ và mã sạch hơn nhiều. Ngoài con trỏ còn hai hướng khác: các kiểu rỗng được của gói cơ sở dữ liệu chuẩn, và tự định nghĩa kiểu riêng có phương thức giải mã.
Kiểu số, số tiền và chuỗi thời gian
JSON chỉ có một kiểu số duy nhất, nên bất kỳ công cụ nào cũng phải đoán khi ánh xạ sang Go. Công cụ này chọn quy tắc đơn giản và dễ kiểm tra: giá trị mẫu là số nguyên thì ra int64, có phần thập phân thì ra float64. Có hai chỗ bạn gần như luôn phải sửa tay. Thứ nhất là số tiền. Đừng để float64 cho tiền, vì kiểu dấu chấm động nhị phân không biểu diễn chính xác được các phân số thập phân, và sau vài phép cộng bạn sẽ thấy số lẻ ở hàng thứ mười lăm rồi báo cáo lệch vài đồng. Hai lối ra quen thuộc là lưu tiền dưới dạng số nguyên tính theo đơn vị nhỏ nhất, hoặc dùng một kiểu số thập phân chính xác từ thư viện ngoài. Thứ hai là số nguyên rất lớn, chẳng hạn mã định danh sinh theo dấu thời gian. Bản thân Go giải mã vào int64 thì vẫn đúng, nhưng nếu ai đó trong chuỗi xử lý đã cho dữ liệu đi qua JavaScript thì con số có thể đã bị làm tròn từ trước, nên hãy kiểm tra nguồn phát. Với chuỗi thời gian, khi công tắc nhận diện bật, chuỗi có hình dạng ngày giờ chuẩn được ánh xạ thành time.Time và dòng nhập gói thời gian được thêm tự động. Cần nhớ giới hạn quan trọng: kiểu time.Time trong thư viện chuẩn chỉ giải mã được chuỗi theo đúng chuẩn RFC 3339. Nếu giao diện lập trình trả về dạng khác, ví dụ ngày tháng năm ngăn bằng dấu gạch chéo, bạn phải khai báo trường là chuỗi rồi tự chuyển, hoặc định nghĩa một kiểu riêng có phương thức giải mã của chính nó.
Trang này khác gì các trang sinh kiểu khác trên site
Bốn trang sinh kiểu từ JSON trên site giải quyết bốn bài toán khác nhau vì bốn ngôn ngữ đích có mô hình khác nhau, nên đừng chọn theo thói quen. Trang bạn đang xem nhắm vào Go, nơi vấn đề trung tâm là sự vắng mặt của kiểu tùy chọn: mọi quyết định về con trỏ, về omitempty và về việc dùng int64 hay float64 đều xoay quanh chuyện đó, và thẻ json là cầu nối duy nhất giữa tên khóa với tên trường. Trang tại /vi/tools/json-to-typescript nhắm vào TypeScript, nơi kiểu chỉ tồn tại lúc biên dịch rồi biến mất, nên câu chuyện ở đó là sự khác nhau giữa thuộc tính tùy chọn với kiểu hợp cùng null, và việc kiểu không hề ràng buộc dữ liệu thật lúc chạy. Trang tại /vi/tools/json-to-python-dataclass nhắm vào Python, nơi bạn phải chọn giữa ba mô hình là dataclass, TypedDict và model kiểm định, và cái bẫy lớn nhất là giá trị mặc định kiểu thay đổi được. Nếu điều bạn cần không phải mã nguồn mà là một bản mô tả cấu trúc dùng để kiểm định ở mọi ngôn ngữ, hãy dựng lược đồ tại /vi/tools/json-schema-generator. Trước khi dán vào bất kỳ trang nào ở trên, nếu khối JSON của bạn còn chú thích hoặc dấu phẩy thừa thì phải làm sạch trước tại /vi/tools/json5-converter, vì các trang sinh mã đều dùng bộ đọc JSON chặt.
Câu hỏi thường gặp (FAQ)
Vì sao trường của tôi luôn rỗng dù phản hồi rõ ràng có dữ liệu?
Hai nguyên nhân thường gặp. Một là trường viết thường chữ đầu nên gói giải mã không nhìn thấy nó, cơ chế phản chiếu chỉ đọc được trường xuất ra ngoài gói. Hai là thẻ json ghi sai tên khóa. Cả hai đều không sinh ra lỗi, hàm giải mã vẫn trả về lỗi rỗng, nên phải tự đối chiếu bằng mắt.
Đặt omitempty lên trường đúng sai có an toàn không?
Không. omitempty bỏ trường khi giá trị bằng giá trị mặc định của kiểu, mà mặc định của kiểu đúng sai là sai. Nghĩa là mọi giá trị sai sẽ biến mất khỏi JSON gửi đi. Nếu bạn cần phân biệt không đặt với đặt bằng sai, hãy khai báo trường là con trỏ tới kiểu đúng sai rồi mới dùng omitempty.
Khi nào nên dùng con trỏ và khi nào để giá trị thường?
Chỉ dùng con trỏ ở những trường mà sự khác nhau giữa không có dữ liệu và có dữ liệu bằng giá trị mặc định thật sự ảnh hưởng tới quyết định của chương trình. Với các trường còn lại, giá trị mặc định là đủ và mã sạch hơn, vì mỗi con trỏ đều buộc bạn kiểm tra khác rỗng trước khi lấy giá trị.
Struct sinh ra có dùng ngược lại để mã hóa ra JSON được không?
Được, thẻ json áp dụng cho cả hai chiều. Nhưng hãy để ý omitempty vì nó chỉ tác động lúc mã hóa ra, và có thể làm biến mất những trường mang giá trị mặc định mà bên nhận đang chờ. Nếu struct dùng cho cả nhận lẫn gửi, hãy cân nhắc tách thành hai struct riêng.
Chuỗi ngày tháng của tôi thành time.Time nhưng chương trình báo lỗi lúc giải mã?
Vì kiểu time.Time trong thư viện chuẩn chỉ đọc được chuỗi đúng chuẩn RFC 3339. Với các dạng khác như ngày tháng năm ngăn bằng dấu gạch chéo, hãy tắt công tắc nhận diện để trường ra kiểu chuỗi rồi tự chuyển, hoặc định nghĩa một kiểu riêng có phương thức giải mã của chính nó.
Mảng lẫn nhiều kiểu ra kết quả gì?
Ra lát cắt của interface rỗng, vì Go không có kiểu hợp. Muốn dùng, bạn phải kiểm tra kiểu thật của từng phần tử bằng câu lệnh chuyển kiểu hoặc câu lệnh rẽ theo kiểu. Nếu được, hãy đề nghị bên phát dữ liệu tách thành hai trường riêng, vì mảng lẫn kiểu gây phiền cho mọi ngôn ngữ tĩnh.
Vì sao số tiền không nên để float64?
Vì dấu chấm động nhị phân không biểu diễn chính xác các phân số thập phân, nên sau vài phép cộng sẽ xuất hiện sai số ở hàng rất nhỏ và báo cáo lệch. Hãy lưu tiền dưới dạng số nguyên tính theo đơn vị nhỏ nhất, hoặc dùng một kiểu số thập phân chính xác từ thư viện ngoài.
Nên chọn struct lồng nội tuyến hay tách thành kiểu riêng?
Tách thành kiểu riêng khi bạn cần truyền cấu trúc con đó vào hàm khác, viết phương thức cho nó, hoặc dùng lại ở nhiều nơi. Để nội tuyến khi cấu trúc chỉ xuất hiện đúng một lần và bạn muốn nhìn thấy toàn bộ hình dạng dữ liệu trong một khối. Nội tuyến sâu quá ba tầng thì rất khó đọc.
Trường trong mẫu mang giá trị rỗng thì kiểu ra là gì?
Ra interface rỗng, vì không có gì để suy kiểu thật. Đây là chỗ bắt buộc sửa tay: thay bằng kiểu thật lấy từ tài liệu hoặc từ một mẫu khác có dữ liệu, rồi quyết định dùng con trỏ hay không. Đừng để nguyên interface rỗng trong mã sản xuất vì nó đẩy việc kiểm tra kiểu sang lúc chạy.
Tên khóa có gạch nối hoặc dấu cách thì xử lý thế nào?
Tên trường được đổi sang dạng viết hoa từng từ, còn tên gốc giữ nguyên trong thẻ json nên việc giải mã vẫn đúng. Điều này khác với nhiều ngôn ngữ khác, nơi tên khóa lạ làm mã sinh ra sai cú pháp. Trong Go, thẻ gánh toàn bộ phần tên gốc.
Công cụ có sinh phương thức giải mã hay mã gọi mạng không?
Không, nó chỉ sinh khai báo struct. Phần gọi mạng, xử lý mã trạng thái, đặt thời gian chờ và kiểm tra lỗi bạn tự viết. Struct sinh ra là điểm bắt đầu để bạn truyền vào hàm giải mã của thư viện chuẩn hoặc thư viện bạn đang dùng.
Dán mảng đối tượng vào thì công cụ xử lý ra sao?
Nó gộp tất cả phần tử lại thành một hình dạng chung, lấy giá trị mẫu đầu tiên khác rỗng cho mỗi khóa, và đánh dấu những khóa không có mặt ở mọi phần tử là tùy chọn. Cuối kết quả có dòng nhắc bạn giải mã vào lát cắt của struct gốc chứ không phải vào chính struct đó.
Từ khóa liên quan
- json to go struct
- chuyển json sang golang struct
- struct tag json golang
- omitempty là gì
- con trỏ trong struct go
- unmarshal json golang
- encoding json go
- tên trường viết hoa go
- quy ước đặt tên golang
- time.Time parse json
- rfc3339 golang
- json.Number golang
- xử lý null trong go
- sql.NullString là gì
- mảng nhiều kiểu trong go
- int64 hay float64 cho tiền
- sinh struct từ api response
- golang decode json lỗi im lặng
- generate go struct from json
- công cụ golang online miễn phí
