README.md

Описание

Кодогенератор создаёт код, который из конструкций вида map[string]interface{} или map[interface{}]interface{} позволяет получать структуру с данными. И соответственно обратно из структуры получить map[string]interface{}. Работает без рефлексии.

Как это работает

Кодогенератор считывает proto файл, выполняет лексический анализ, далее синтаксический анализ и семантический, обходя дерево синтаксиса выражает модель proto с помощью Java классов. В модели классов написаны методы генерации, кокторые создают код маппера. Лексический анализ (токенезация) проводится с помощью регулярных выражений jFlex, семантический, используя Yacc и правила производства (построение дерева синтаксиса).

Поддержка языков

Имея модель с данными в виде Java классов можно написать кодогенератор для любых языков. На данный момент меня интересовала генерация для Go. На выходе получается маппер из map в struct и обратно. Для генерации под другой язык достаточно по аналогии реализовать generateGolangToMap и generateGolangFromMap (пакет model).

Использование

Создать файл schema.proto, и выполнить java -jar Protogen.jar на выходе будет создан каталог go, там и будет результат выполнения.

Возможные ошибки

При разработке кодогенератора не стояло цели реализовать 100% разбор proto файла, поэтому ограничьтесь только package и message. В конечном счёте это только инструмент, помощник.

Так сложилось

Проект разрабатывается с помощью Apache NetBeans, понимаю ваше негодование, но так сложилось.

Пример входного файла и результата кодогенерации

Исходный файл:

syntax = "proto3";
package model;

message ProductPrice {
    int64 ProductId = 1;
    int64 PriceId = 2;
    optional string Description = 3;
    repeated int64 PriceCharacteristicIds = 4;
}

Результат кодогенерации:

// generated by protogen
package model

type ProductPrice struct {
    ProductId int64
    PriceId int64
    Description *string
    PriceCharacteristicIds []int64
}

func (s ProductPrice) ToMap() (map[string]interface{}) {
    m := make(map[string]interface{})
    m["product_id"] = s.ProductId
    m["price_id"] = s.PriceId
    if s.Description != nil {
        m["description"] = *(s.Description)
    }
    PriceCharacteristicIds := make([]interface{}, 0)
    for _, e := range s.PriceCharacteristicIds {
        PriceCharacteristicIds = append(PriceCharacteristicIds, e)
    }
    m["price_characteristic_ids"] = PriceCharacteristicIds
    return m
}

func MapProductPrice(i interface{}) (ProductPrice, bool) {
	var p ProductPrice
	if m, ok := cast.ToMap(i); ok {
		if v, ok := cast.ToInt64(m["product_id"]); ok {
			p.ProductId = v
		} else {
			return p, false
		}
		if v, ok := cast.ToInt64(m["price_id"]); ok {
			p.PriceId = v
		} else {
			return p, false
		}
		if v, ok := cast.ToString(m["description"]); ok {
			p.Description = &v
		}
		if PriceCharacteristicIds, ok := cast.ToSlice(m["price_characteristic_ids"]); ok {
			for _, e := range PriceCharacteristicIds {
				if v, ok := cast.ToInt64(e); ok {
					p.PriceCharacteristicIds = append(p.PriceCharacteristicIds, v)
				}
			}
		}
		return p, true
	}
	return p, false
}

Пример кода использующего результат кодогенерации

Пример кода:

func main() {
	productPrice1 := model.ProductPrice{
		ProductId: 1,
		PriceId:   2,
	}

	fmt.Println(productPrice1.ToMap())

	srcProductPrice := map[string]interface{}{
		"product_id":               12,
		"price_id":                 34,
		"description":              "Привет",
		"price_characteristic_ids": []interface{}{5446, 5447, 5449},
	}

	if productPrice, ok := model.MapProductPrice(srcProductPrice); ok {
		fmt.Println(productPrice)
	}
}

Вывод в консоль:

map[price_characteristic_ids:[] price_id:2 product_id:1]
{12 34 0xc000028300 [5446 5447 5449]}

Что еще потребуется

Потребуется написать небольшую библиотеку «cast», это очень просто. Вот несколько заготовок:

cast_int64.go:

package cast

func ToInt64(i interface{}) (int64, bool) {
	switch v := i.(type) {
	case int64:
		return v, true
	case int:
		return int64(v), true
	case int32:
		return int64(v), true
    // ...
	}

	return 0, false
}

cast_map.go:

package cast

func ToMap(i interface{}) (map[string]interface{}, bool) {
	switch e := i.(type) {
	case map[string]interface{}:
		return e, true
	case map[interface{}]interface{}:
		m := map[string]interface{}{}
		for k, v := range e {
			m[k.(string)] = v
		}
		return m, true
	}

	return nil, false
}

cast_slice.go:

package cast

func ToSlice(i interface{}) ([]interface{}, bool) {
	switch e := i.(type) {
	case []interface{}:
		return e, true
	}

	return nil, false
}

cast_string.go

package cast

func ToString(i interface{}) (string, bool) {
	switch v := i.(type) {
	case string:
		return v, true
	}

	return "", false
}

Описание

Кодогенератор модели и маппера на основе proto файла

Релизы
Protogen v1.1 2024-04-18
Конвейеры
0 успешных
0 с ошибкой