Trong bài trước, tôi chỉ đơn giản in hoa những gì nhận được và gửi lại thôi. Bot thực hiện mỗi chuyện đó thì đơn giản quá nên một số tính năng hay của nền tảng Messenger của Facebook cung cấp như menu, trả lời nhanh, xử lý postback, v.v... chưa được sử dụng. Trong bài này tôi sẽ cho bot thực hiện chức năng phức tạp hơn tí là hiển thị tỉ giá ngoại tệ với đồng Việt Nam. Nào chúng ta bắt đầu!
Đầu tiên chúng ta cần tìm một nơi có thể cung cấp tỉ giá thường xuyên cập nhật để có thể lấy thông tin. Tôi tìm được URL của ngân hàng ngoại thương (VCB) cung cấp dạng XML và ngân hàng Đông Á cung cấp dạng JSON. Tôi sẽ thử với dữ liệu của VCB, của Đông Á các bạn tự làm nhé.
Xử lý dữ liệu tỉ giá
Các chức năng của bot phức tạp lên nên tôi tách hẳn những xử lý này ra file bot.go luôn cho tiện quản lý. Lúc này phần xử lý các thuộc tính Messaging trong processWebhook tôi sẽ gom chung trong hàm processMessage để ở bot.go luôn. Lúc này hàm processWebhook ở main.go đơn giản như sau:
func processWebhook(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var req Request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
w.WriteHeader(404)
w.Write([]byte("Message not supported"))
return
}
if req.Object == "page" {
for _, entry := range req.Entry {
for _, event := range entry.Messaging {
if event.Message != nil {
processMessage(&event)
}
}
}
w.WriteHeader(200)
w.Write([]byte("Got your message"))
} else {
w.WriteHeader(404)
w.Write([]byte("Message not supported"))
}
}
Giờ mọi xử lý chúng ta tập trung ở bot.go, không đụng gì đến main.go nữa.
Đầu tiên là hàm xử lý lấy thông tin tỉ giá. Tỉ giá trả về dạng XML nên tôi khai báo struct và hàm xử lý như bên dưới:
Đầu tiên chúng ta cho người dùng chọn ngoại tệ họ muốn xem tỉ giá. Có nhiều cách làm việc này:
(1) Hiển thị danh sách các ngoại tệ, người dùng gõ viết tắt ngoại tệ nào thì bot trả lời thông tin tỉ giá ngoại tệ đó. Ví dụ họ gõ USD, bot trả về thông tin tỉ giá giữa đô la Mĩ và đồng Việt Nam. Cách này đơn giản nhất nhưng có điểm dở là người dùng sẽ gõ nhiều kiểu khác nhau và thậm chí gõ sai thì chúng ta xử lý rất mệt. Ví dụ: gõ usd hay usđ, ...
(2) Hiển thị danh sách ngoại tệ kèm thêm con số, người dùng nhập số tương ứng thì bot xử lý. Cách này tiện hơn cách trên vì nhập số ít sai sót nhưng nó chỉ phù hợp với các nền tảng chat chỉ hỗ trợ chat chuỗi văn bản.
(3) Hiển thị danh sách ngoại tệ dạng câu trả lời nhanh. Cách này khá đơn giản khi phát triển nhưng cũng khá tiện lợi cho người dùng nên tôi sẽ chọn nó cho chức năng này. Điểm dở của nó là danh sách trả lời nhanh này sẽ mất đi khi người dùng chọn câu trả lời nên không chọn lại được nữa. Tối đa hiển thị được 11 câu trả lời nhanh. Lưu ý trả lời nhanh không có chạy trên Messenger Lite.
(4) Hiển thị ngoại tệ dạng danh sách cuộn. Cách này thường dùng để cung cấp các thông tin có nhiều nội dung và hình ảnh đi kèm. Mỗi lần hiển thị được 10 mục. Tôi thấy hiển thị danh sách của Đông Á dùng cách này khá ổn do nó có cả hình ảnh là lá cờ các quốc gia nữa. Tài liệu về danh sách cuộn ở đây. Các bạn làm thử nhé!
Dựa trên tài liệu Facebook tôi sửa lại struct Message và bổ sung thêm struct QuickReply như sau:
Giờ chạy lại để tận hưởng thành quả nào!
Như vậy chúng ta đã hoàn thành chatbot server cung cấp chức năng thông tin tỉ giá ngoại tệ. Giờ chúng ta cùng tìm hiểu cách tạo menu và xử lý postback nhé.
Màn hình chào là màn hình hiển thị khi một người dùng mở cửa sổ chat với page lần đầu tiên hoặc ngay sau khi họ chọn xóa dữ liệu chat với page. Màn hình này có 1 lời chào và một nút "Bắt đầu" hoặc "Get Started" để giúp người dùng chọn tránh bỡ ngỡ ban đầu. Khi họ bấm nút, bot sẽ được 1 sự kiện postback
Menu là danh sách các chức năng luôn hiển thị để người dùng có thể chọn nhanh chức năng họ cần. Khi họ chọn, bot sẽ nhận được sự kiện postback tương ứng.
Dựa trên tài liệu Facebook liên quan ở trên, tôi khai báo các struct và tạo hàm thiết lập màn hình chào và menu như sau:
Sau khi chạy, refresh màn hình Facebook hoặc Messenger chúng ta thấy menu dạng hamburger xuất hiện:
Muốn thấy nút "Bắt đầu" chúng ta phải xóa cuộc trò chuyện như hướng dẫn sau:
Sau khi đồng ý xóa cuộc trò chuyện và refresh lại trang, chúng ta sẽ thấy nút bắt đầu và lời chào xuất hiện:
Chắc hẳn bạn nóng lòng muốn chọn nó nhưng tôi khuyên là chưa vội vì chúng ta chưa hề xử lý gì khi ấn vào nó cả. Như tôi đề cập ở trên, khi ấn vào nút "Bắt đầu" hoặc item của menu thì Facebook gửi Postback cho bot nên chúng ta phải xử lý nó trước đã. Các bạn đọc tài liệu về Postback ở đây. Việc chúng ta bây giờ gồm 3 việc:
- Bổ sung thuộc tính Postback vào Messaging để nhận được postback do Facebook gửi tới.
- Sửa lại hàm processWebhook để nhận thông tin postback.
- Viết hàm processPostback để xử lý postback.
Đầu tiên, struct Messaging trong message.go được sửa lại như sau:
Bây giờ chạy thử các bạn sẽ nhận được kết quả như ý khi nhấn bắt đầu hay chọn từ menu:
Như vậy là chúng ta đã hoàn thành các chức năng cơ bản cho 1 chatbot trên Facebook Messenger viết bằng Go. Còn 1 số phần khác nền tảng Messenger hỗ trợ hay cách đăng ký để Facebook xác nhận để mọi người có thể tương tác với bot của bạn các bạn tự nghiên cứu nhé. Nếu có vấn đề cứ comment bên dưới. Trả lời được tôi sẽ trả lời.
Toàn bộ mã nguồn của phần này tôi để ở đây.
Trong bài tới tôi sẽ giới thiệu về cách triển khai chatbot server lêm dịch vụ Lambda của Amazon Web Services.
package mainGiờ là lúc sửa lại hàm processMessage để cung cấp chức năng về thông tin tỉ giá.
import (
"encoding/xml"
"net/http"
"strings"
"github.com/apex/log"
)
type (
ExchangeRate struct {
DateTime string `xml:"DateTime"`
Exrate []Exrate `xml:"Exrate"`
Source string `xml:"Source"`
}
Exrate struct {
CurrencyCode string `xml:"CurrencyCode,attr"`
CurrencyName string `xml:"CurrencyName,attr"`
Buy string `xml:"Buy,attr"`
Transfer string `xml:"Transfer,attr"`
Sell string `xml:"Sell,attr"`
}
)
func processMessage(event *Messaging) {
sendAction(event.Sender, MarkSeen)
sendAction(event.Sender, TypingOn)
sendText(event.Sender, strings.ToUpper(event.Message.Text))
sendAction(event.Sender, TypingOff)
}
func getExchangeRateVCB() (*ExchangeRate, bool) {
var exrate ExchangeRate
req, err := http.NewRequest("GET", "http://www.vietcombank.com.vn/ExchangeRates/ExrateXML.aspx", nil)
if err != nil {
log.Errorf("getExchangeRateVCB: NewRequest: %s", err.Error())
return &exrate, false
}
client := &http.Client{Timeout: time.Second * 30}
resp, err := client.Do(req)
if err != nil {
log.Errorf("getExchangeRateVCB: client.Do: %s", err.Error())
return &exrate, false
}
defer resp.Body.Close()
err = xml.NewDecoder(resp.Body).Decode(&exrate)
if err != nil {
log.Errorf("getExchangeRateVCB: xml.NewDecoder: %s", err.Error())
return &exrate, false
}
return &exrate, true
}
Đầu tiên chúng ta cho người dùng chọn ngoại tệ họ muốn xem tỉ giá. Có nhiều cách làm việc này:
(1) Hiển thị danh sách các ngoại tệ, người dùng gõ viết tắt ngoại tệ nào thì bot trả lời thông tin tỉ giá ngoại tệ đó. Ví dụ họ gõ USD, bot trả về thông tin tỉ giá giữa đô la Mĩ và đồng Việt Nam. Cách này đơn giản nhất nhưng có điểm dở là người dùng sẽ gõ nhiều kiểu khác nhau và thậm chí gõ sai thì chúng ta xử lý rất mệt. Ví dụ: gõ usd hay usđ, ...
(2) Hiển thị danh sách ngoại tệ kèm thêm con số, người dùng nhập số tương ứng thì bot xử lý. Cách này tiện hơn cách trên vì nhập số ít sai sót nhưng nó chỉ phù hợp với các nền tảng chat chỉ hỗ trợ chat chuỗi văn bản.
(3) Hiển thị danh sách ngoại tệ dạng câu trả lời nhanh. Cách này khá đơn giản khi phát triển nhưng cũng khá tiện lợi cho người dùng nên tôi sẽ chọn nó cho chức năng này. Điểm dở của nó là danh sách trả lời nhanh này sẽ mất đi khi người dùng chọn câu trả lời nên không chọn lại được nữa. Tối đa hiển thị được 11 câu trả lời nhanh. Lưu ý trả lời nhanh không có chạy trên Messenger Lite.
(4) Hiển thị ngoại tệ dạng danh sách cuộn. Cách này thường dùng để cung cấp các thông tin có nhiều nội dung và hình ảnh đi kèm. Mỗi lần hiển thị được 10 mục. Tôi thấy hiển thị danh sách của Đông Á dùng cách này khá ổn do nó có cả hình ảnh là lá cờ các quốc gia nữa. Tài liệu về danh sách cuộn ở đây. Các bạn làm thử nhé!
Xử lý trả lời nhanh
Dựa trên tài liệu Facebook tôi sửa lại struct Message và bổ sung thêm struct QuickReply như sau:
type (Cần phải viết thêm hàm để gửi câu trả lời nhanh nữa. Do gửi trả lời nhanh chỉ hơn gửi văn bản ở phần QuickReply nên tôi chỉnh lại hàm sendText luôn cho đồng nhất như sau:
...
Message struct {
MID string `json:"mid,omitempty"`
Text string `json:"text,omitempty"`
QuickReply *QuickReply `json:"quick_reply,omitempty"`
}
QuickReply struct {
ContentType string `json:"content_type,omitempty"`
Title string `json:"title,omitempty"`
Payload string `json:"payload"`
}
ResMessage struct {
Text string `json:"text,omitempty"`
QuickReply []QuickReply `json:"quick_replies,omitempty"`
}
...
)
func sendTextWithQuickReply(recipient *User, message string, replies []QuickReply) error {Để đơn giản tôi quy định là nếu người dùng gõ vào "rate" thì chức năng tỉ giá này sẽ được bot xử lý. Hàm processMessage được sửa lại như sau:
m := ResponseMessage{
MessageType: MessageResponse,
Recipient: recipient,
Message: &ResMessage{
Text: message,
QuickReply: replies,
},
}
return sendFBRequest(FBMessageURL, &m)
}
func sendText(recipient *User, message string) error {
sendTextWithQuickReply(user, message, nil)
}
var (Tôi có ghi chú rồi nên chắc không cần nói gì hơn, chỉ lưu ý ở exRateList. Biến toàn cục này được cập nhật mỗi lần có người nhắn "rate" để luôn có cập nhật mới nhưng cũng giảm được số lần lấy dữ liệu nếu so với việc mỗi lần xử lý là lại lấy. Tuy nhiên cách này có chỗ dở là nếu người khác nhắn "rate" nó cũng được cập nhật nên nếu lúc đó bot thao tác cho người dùng này thì có thể bị lỗi. Giải quyết trường hợp này có thể dùng mutex.
// Lưu thông tin ngoại tệ lấy được
exRateList *ExchangeRate
// Lưu nhóm ngoại tệ đang hiển thị của từng người dùng
exRateGroupMap = make(map[string]int)
)
func processMessage(event *Messaging) {
// Gửi hành động đã xem và đang trả lời
sendAction(event.Sender, MarkSeen)
sendAction(event.Sender, TypingOn)
// Xử lý khi người dùng chọn trả lời nhanh
if event.Message.QuickReply != nil {
processQuickReply(event)
return
}
// Xử lý khi người dùng gửi văn bản
text := strings.ToLower(strings.TrimSpace(event.Message.Text))
if text == "rate" {
// Lưu nhóm ngoại tệ xem hiện tại
exRateGroupMap[event.Sender.ID] = 1
// Gửi danh sách ngoại tệ
sendExchangeRateList(event.Sender)
} else {
// Gửi chuỗi nhận được sau khi chuyển sang chữ hoa
sendText(event.Sender, strings.ToUpper(event.Message.Text))
}
// Gửi hành động đã trả lời xong
sendAction(event.Sender, TypingOff)
}
func processQuickReply(event *Messaging) {
recipient := event.Sender
exRateGroup := exRateGroupMap[event.Sender.ID]
switch event.Message.QuickReply.Payload {
case "Next": // Trường hợp người dùng chọn "Xem tiếp"
var i int
// Kiểm tra nếu đã xem xong danh sách thì quay lại
if exRateGroup*10 >= len(exRateList.Exrate) {
exRateGroup = 1
} else {
exRateGroup++
}
exRateGroupMap[event.Sender.ID] = exRateGroup
quickRep := []QuickReply{}
// Mỗi lần hiển thị gồm 10 ngoại tệ
for i = 10 * (exRateGroup - 1); i < 10*exRateGroup && i < len(exRateList.Exrate); i++ {
exrate := exRateList.Exrate[i]
quickRep = append(quickRep, QuickReply{ContentType: "text", Title: exrate.CurrencyName, Payload: exrate.CurrencyCode})
}
// Thêm nút "Xem tiếp"
quickRep = append(quickRep, QuickReply{ContentType: "text", Title: "Xem tiếp", Payload: "Next"})
sendTextWithQuickReply(recipient, "GoBot cung cấp chức năng xem tỉ giá giữa các ngoại tệ và đồng Việt Nam.\nMời bạn chọn ngoại tệ:", quickRep)
default: // Trường hợp người dùng chọn 1 nút trả lời nhanh
var exRate Exrate
// Kiểm tra coi payload nhận được khớp với item nào
for i := 10 * (exRateGroup - 1); i < 10*exRateGroup && i < len(exRateList.Exrate); i++ {
if exRateList.Exrate[i].CurrencyCode == event.Message.QuickReply.Payload {
exRate = exRateList.Exrate[i]
break
}
}
// Không tìm thấy item nào khớp
if len(exRate.CurrencyCode) == 0 {
sendText(recipient, "Không có thông tin về ngoại tệ này")
return
}
// Trả về thông tin tìm được
sendText(recipient, fmt.Sprintf("%s-VND\nGiá mua: %sđ\nGiá bán: %sđ\nGiá chuyển khoản: %sđ", exRate.CurrencyCode, exRate.Buy, exRate.Sell, exRate.Transfer))
}
}
func sendExchangeRateList(recipient *User) {
var (
ok bool
i int
exRateGroup = exRateGroupMap[recipient.ID]
)
// Lấy danh sách ngoại tệ và lưu vào biến toàn cục exRateList
exRateList, ok = getExchangeRateVCB()
if !ok {
sendText(recipient, "Có lỗi trong quá trình xử lý. Bạn vui lòng thử lại sau bằng cách gửi 'rate' cho tôi nhé. Cảm ơn!")
return
}
quickRep := []QuickReply{}
// Lấy nhóm 10 ngoại tệ
for i = 10 * (exRateGroup - 1); i < 10*exRateGroup && i < len(exRateList.Exrate); i++ {
exrate := exRateList.Exrate[i]
quickRep = append(quickRep, QuickReply{ContentType: "text", Title: exrate.CurrencyName, Payload: exrate.CurrencyCode})
}
quickRep = append(quickRep, QuickReply{ContentType: "text", Title: "Xem tiếp", Payload: "Next"})
sendTextWithQuickReply(recipient, "GoBot cung cấp chức năng xem tỉ giá giữa các ngoại tệ và đồng Việt Nam.\nMời bạn chọn ngoại tệ:", quickRep)
}
Giờ chạy lại để tận hưởng thành quả nào!
Như vậy chúng ta đã hoàn thành chatbot server cung cấp chức năng thông tin tỉ giá ngoại tệ. Giờ chúng ta cùng tìm hiểu cách tạo menu và xử lý postback nhé.
Màn hình chào và menu
Màn hình chào là màn hình hiển thị khi một người dùng mở cửa sổ chat với page lần đầu tiên hoặc ngay sau khi họ chọn xóa dữ liệu chat với page. Màn hình này có 1 lời chào và một nút "Bắt đầu" hoặc "Get Started" để giúp người dùng chọn tránh bỡ ngỡ ban đầu. Khi họ bấm nút, bot sẽ được 1 sự kiện postback
Menu là danh sách các chức năng luôn hiển thị để người dùng có thể chọn nhanh chức năng họ cần. Khi họ chọn, bot sẽ nhận được sự kiện postback tương ứng.
Dựa trên tài liệu Facebook liên quan ở trên, tôi khai báo các struct và tạo hàm thiết lập màn hình chào và menu như sau:
Hàm registerGreetingnMenu khi nào cần thay đổi chúng ta mới gọi bởi vì mỗi khi gọi, menu sẽ không có hiệu lực ngay mà phải refresh mới thấy. Do đó tôi thường để nó đầu hàm main, sau đó return luôn để chạy mỗi nó. Chạy xong thì comment 2 dòng này để hàm main hoạt động bình thường lại như trước.
type (
PageProfile struct {
Greeting []Greeting `json:"greeting,omitempty"`
GetStarted *GetStarted `json:"get_started,omitempty"`
PersistentMenu []PersistentMenu `json:"persistent_menu,omitempty"`
}
Greeting struct {
Locale string `json:"locale,omitempty"`
Text string `json:"text,omitempty"`
}
GetStarted struct {
Payload string `json:"payload,omitempty"`
}
PersistentMenu struct {
Locale string `json:"locale"`
Composer bool `json:"composer_input_disabled"`
CTAs []CTA `json:"call_to_actions"`
}
CTA struct {
Type string `json:"type"`
Title string `json:"title"`
URL string `json:"url,,omitempty"`
Payload string `json:"payload"`
CTAs []CTA `json:"call_to_actions,omitempty"`
}
)
const (
GetStartedPB = "GetStarted"
RatePB = "rate"
)
func registerGreetingnMenu() bool {
profile := PageProfile{
Greeting: []Greeting{
{
Locale: "default",
Text: "Dịch vụ cung cấp thông tin tỉ giá hối đoái",
},
},
GetStarted: &GetStarted{Payload: GetStartedPB},
PersistentMenu: []PersistentMenu{
{
Locale: "default",
Composer: false,
CTAs: []CTA{
{
Type: "postback",
Title: "Tỉ giá hối đoái",
Payload: RatePB,
},
},
},
},
}
err := sendFBRequest("https://graph.facebook.com/v3.1/me/messenger_profile", &profile)
if err != nil {
log.Error("registerGreetingnMenu:" + err.Error())
return false
}
return true
}
Sau khi chạy, refresh màn hình Facebook hoặc Messenger chúng ta thấy menu dạng hamburger xuất hiện:
Sau khi đồng ý xóa cuộc trò chuyện và refresh lại trang, chúng ta sẽ thấy nút bắt đầu và lời chào xuất hiện:
Chắc hẳn bạn nóng lòng muốn chọn nó nhưng tôi khuyên là chưa vội vì chúng ta chưa hề xử lý gì khi ấn vào nó cả. Như tôi đề cập ở trên, khi ấn vào nút "Bắt đầu" hoặc item của menu thì Facebook gửi Postback cho bot nên chúng ta phải xử lý nó trước đã. Các bạn đọc tài liệu về Postback ở đây. Việc chúng ta bây giờ gồm 3 việc:
- Bổ sung thuộc tính Postback vào Messaging để nhận được postback do Facebook gửi tới.
- Sửa lại hàm processWebhook để nhận thông tin postback.
- Viết hàm processPostback để xử lý postback.
Đầu tiên, struct Messaging trong message.go được sửa lại như sau:
type (Hàm processWebhook trong main.go:
...
Messaging struct {
Sender *User `json:"sender,omitempty"`
Recipient *User `json:"recipient,omitempty"`
Timestamp int `json:"timestamp,omitempty"`
Message *Message `json:"message,omitempty"`
PostBack *PostBack `json:"postback,omitempty"`
}
PostBack struct {
Title string `json:"title,omitempty"`
Payload string `json:"payload"`
}
...
)
func processWebhook(w http.ResponseWriter, r *http.Request) {Hàm processPostback ở bot.go:
defer r.Body.Close()
var req Request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
w.WriteHeader(404)
w.Write([]byte("Message not supported"))
return
}
if req.Object == "page" {
for _, entry := range req.Entry {
for _, event := range entry.Messaging {
if event.Message != nil {
processMessage(&event)
} else if event.PostBack != nil {
processPostBack(&event)
}
}
}
w.WriteHeader(200)
w.Write([]byte("Got your message"))
} else {
w.WriteHeader(404)
w.Write([]byte("Message not supported"))
}
}
func processPostBack(event *Messaging) {Để đơn giản thì việc người dùng chọn "Bắt đầu" hay từ menu đều xử lý cung cấp thông tin tỉ giá cả. Sau này nếu các bạn bổ sung chức năng khác thì tách riêng ra sau nhé.
// Gửi hành động đã xem và đang trả lời
sendAction(event.Sender, MarkSeen)
sendAction(event.Sender, TypingOn)
switch event.PostBack.Payload {
case GetStartedPB, RatePB:
exRateGroupMap[event.Sender.ID] = 1
sendExchangeRateList(event.Sender)
}
// Gửi hành động đã trả lời xong
sendAction(event.Sender, TypingOff)
}
Bây giờ chạy thử các bạn sẽ nhận được kết quả như ý khi nhấn bắt đầu hay chọn từ menu:
Như vậy là chúng ta đã hoàn thành các chức năng cơ bản cho 1 chatbot trên Facebook Messenger viết bằng Go. Còn 1 số phần khác nền tảng Messenger hỗ trợ hay cách đăng ký để Facebook xác nhận để mọi người có thể tương tác với bot của bạn các bạn tự nghiên cứu nhé. Nếu có vấn đề cứ comment bên dưới. Trả lời được tôi sẽ trả lời.
Toàn bộ mã nguồn của phần này tôi để ở đây.
Trong bài tới tôi sẽ giới thiệu về cách triển khai chatbot server lêm dịch vụ Lambda của Amazon Web Services.
Tóm tắt:
- Bot cung cấp chức năng thông tin tỉ giá hối đoái, lấy dữ liệu từ VCB dạng XML.
- Trả lời nhanh là công cụ nền tảng Messenger hỗ trợ giúp tạo các nút bên dưới nội dung chat để người dùng có thể chọn cho câu trả lời của họ mà không phải gõ. Lưu ý: Các lựa chọn mất sau khi người dùng chọn, tối đa 11 lựa chọn và không hiển thị được ở Messenger Lite.
- Màn hình chào hiển thị khi người dùng lần đầu chat với page và có nút "Bắt đầu" để kích hoạt tương tác giữa người dùng và chatbot.
- Menu là công cụ luôn hiển thị để người dùng chọn khi cần đến nhanh một chức năng nào đó.
- Khi người dùng ấn chọn nút "Bắt đầu" hoặc menu, bot sẽ nhận được sự kiện postback.
Em thử xử lí file JSON của đông á nhưng mà trong file đó nó có ( ở đầu và ) ở cuối. thành ra nó không convert từ JSON sang struct được. có cách nào để mình loại bỏ mấy cái dấu đó trước khi convert không ạ?
ReplyDeleteGiả sử em đọc được file đó đưa vào string s thì chuỗi cần Unmarshal sẽ là s[1:len(s)-1]
Deletedạ. e làm theo cách đó được r a. e đang hi vọng nhận thêm được những cách khác nữa. ^^ em cảm ơn a
DeleteChuyện đó em có thể suy nghĩ để tìm ra được đó. Ví dụ 1 cách khác json không có dùng dấu ngoặc tròn () nên có thể dùng thư viện strings để loại bỏ tất cả những ký tự ( và ) trong chuỗi.
Delete