Я делюсь простой библиотекой, которую я постоянно использую. Go хорошо работает с JSON, но часто не хватает набора функций для приведения interface{} к какому-то типу. Даже определив канонично структуру для маршалинга JSON, со временем приходится определять дополнительное поле, назвав его Extra interface{}. Вот примерно, что мы имеем на практике.
type Message struct {
store bool
Type string `json:"type"`
Session string `json:"session,omitempty"`
Data map[string]string `json:"data,omitempty"`
Text string `json:"text,omitempty"`
Name string `json:"name,omitempty"`
Time int64 `json:"time,omitempty,string"`
ServerId int64 `json:"serverId,omitempty,string"`
Extra interface{} `json:"extra,omitempty"`
}
Почему каноничный подход плохо работает. Основной источник JSON это JavaScript из веб броузера. JavaScript все числа представляет как double. JavaScript может округлить int64, к примеру
fmt.Println(time.Now().UnixNano())
выдаст 1428308621182823638
javascript:alert(1428308621182823638)
а это уже
поэтому некоторые числа можно определять как строки, используя тег `json:",string"`, но это не будет работать, если пользователь не поставит вокруг числа кавычки.
package main
import (
"encoding/json"
"fmt"
)
type X struct {
Time int64 `json:"time,omitempty,string"`
}
func main() {
var m1 map[string]interface{}
e := json.Unmarshal([]byte(`{"x":1,"y":{}}`), &m1)
fmt.Println(e, m1)
var x X
e = json.Unmarshal([]byte(`{"time":1}`), &x)
fmt.Println(e, x)
e = json.Unmarshal([]byte(`{"time":"1"}`), &x)
fmt.Println(e, x)
}
{«time»:1} — не сработало
Трудно представить сколько сил потратит суппорт веб сервиса, даже когда в документации написать жирным курсивом, что числа надо передавать в кавычках, то все равно люди будут их передавать и так и так.
Обратная ситуация тоже возможна, если вам нужны числа в JSON, то пользователи могут передать число как строку, ведь (в ПХП работает) тег <input /> возвращает введенные числа как строки, значит в JSON веб сервиса вместо чисел могут попасть строки.
Часто проще быстрей написать
var m1 map[string]interface{}
e := json.Unmarshal([]byte(`{"x":1,"y":{}}`), &m1)
fmt.Println(e, m1)
и тут не хватает лаконичных функций для приведения interface{} к string или []interface{}, вот их я и определил в своем пакете pyraconv.
Мои функции не возвращают nil. К примеру, pyraconv.ToStringArray() всегда вернет []string{} вместо nil, pyraconv.ToInt64() — всегда вернет один параметр int64 без ошибки, а значит можно написать, int(pyraconv.ToInt64(x))
Я не претендую на то, что таким образом следует парсить любой JSON всегда и везде, но нахожу этот код весьма полезным.
Список функций:
func ToBool(i1 interface{}) bool
ToBool конвертирует interface{} в bool
func ToInt64(i1 interface{}) int64
ToInt64 конвертирует interface{} в int64
func ToInterfaceArray(i1 interface{}) []interface{}
ToInterfaceArray конвертирует interface{} в []interface{} и не возвращает nil
func ToInterfaceMap(i1 interface{}) map[string]interface{}
ToInterfaceMap конвертирует interface{} в map[string]interface{} и не возвращает nil
func ToString(i1 interface{}) string
ToString конвертирует interface{} в string
func ToStringArray(i1 interface{}) []string
ToStringArray конвертирует interface{} в []string и не возвращает nil
func ToStringMap(i1 interface{}) map[string]string
ToStringMap конвертирует interface{} в map[string]string и не возвращает nil
func CloneObject(a, b interface{})
CloneObject создает копию объекта используя сериализацию из пакета gob. Бывает эффективней передать копию объекта в гоурутину для параллельной обработки, чем использовать механизм блокировок.
Пример хендлера веб сервиса с использованием pyraconv при обработке JSON.
func handle_ctrl_channel(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
b, e := ioutil.ReadAll(r.Body)
if e != nil {
fmt.Fprintf(w, `{ "error": true, "no":1 }`)
return
}
var m1 map[string]interface{}
e = json.Unmarshal(b, &m1)
if e != nil {
fmt.Fprintf(w, `{ "error": true, "no":2 }`)
return
}
cmd := m1["cmd"]
if cmd == "add" {
id := pyraconv.ToString(m1["id"])
url := pyraconv.ToStringArray(m1["urls"])
service.UpdateStream(url, id)
return
}
if cmd == "delete" {
id := pyraconv.ToString(m1["id"])
service.DelStream(id)
return
}
}
}
Форкайте pyraconv на здоровье. Приятного аппетита.
go get github.com/CossackPyra/pyraconv
Автор: pyra