Приведение типов на 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
    
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 11
    • 0
      Всё гораздо проще) http://play.golang.org/p/BFWtPlSuBC
      • 0
        Но маршалится оно не в строку, даже с тегом string play.golang.org/p/mqdo4HjqDf, а число, а джаваскрипт округлит это число,

        не всегда проще определять структуру для парсинга JSON, так как сервис может обрабатывать JSON, в котором поле X можеть быть разных типов: [] или {}, взависимости от поля Yhttp://habrahabr.ru/post/255043/#
        • 0
          Как вы думаете, какой тип будет у m1[«x»] в вашем примере?
          http://play.golang.org/p/hPm0PFobdo
          Так что опять проблема.
          • 0
            да да, и это стандарт, а фактически используют это поле как целое

            Эта проблема не мной придумана, Посмотрите на JSON фейсбука (или JSONP). У них встречаются 15 значные числа без кавычек это поле «u», и 16 значный (не 16ричные) значение в кавычках, это поле «entidentifier». (запрос pull, вероятно чат фейсбука)

            • –1
              Проблема в том, что число из примера не помещается во float64.
              • +1
                Оно не то, чтобы не помещается, оно не представимо в double. Немного разные вещи. Извиняюсь за занудство.
                • 0
                  Ну, если и дальше позанудствовать, то в стандарте JSON про double ничего не сказано.
                  • 0
                    Ну, я не думаю, что в текстовом формате есть смысл привязываться к какому-то непонятному IEEE 754, да и вообще — к каким-либо нативным типам данных какой-либо архитектуры :)
        • 0
          Но маршалится оно не в строку

          Использовать для мршалинга отдельную структуру? play.golang.org/p/PpL9mmVYtW
        • 0
          Благодаря вашему предложению я добавил поддержку json.Number в либу. Спасибо

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое