Приведение типов на Go

от автора

Я делюсь простой библиотекой, которую я постоянно использую. 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) } 

Playground

{«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 

ссылка на оригинал статьи http://habrahabr.ru/post/255043/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *