- Published on
Go struct to OpenSearch mapping
1766 words9 min read
package main
import (
"fmt"
"reflect"
"regexp"
"strings"
)
type Time struct {
Time string
}
type DoubleNestedThing struct {
DoubleNestedA string `json:"doubleNestedA,omitempty"`
DoubleNestedB *string `json:"doubleNestedB,omitempty"`
DoubleNestedC *bool `json:"doubleNestedC,omitempty"`
}
type InlineThing struct {
InlineA string `json:"inlineA,omitempty"`
InlineB string `json:"inlineB,omitempty"`
}
type SliceThing struct {
SliceA string `json:"sliceA,omitempty"`
SliceB int `json:"sliceB,omitempty"`
SliceC map[string]string `json:"sliceC,omitempty"`
}
type NestedThing struct {
NestedA string `json:"nestedA,omitempty"`
NestedB []string `json:"nestedB,omitempty"`
NestedC DoubleNestedThing `json:"nestedC,omitempty"`
NestedD []SliceThing `json:"nestedD,omitempty"`
}
type PointerThing struct {
PointerA *string `json:"pointerA,omitempty"`
PointerB *bool `json:"pointerB,omitempty"`
PointerC *int `json:"pointerC,omitempty"`
PointerD *float32 `json:"pointerD,omitempty"`
}
type RepeatedThing struct {
RepeatedA string `json:"repeatedA,omitempty"`
RepeatedB string `json:"repeatedB,omitempty"`
}
type AppReleaseType string
const (
AppReleaseTypeUnreleased AppReleaseType = "Unreleased"
AppReleaseTypeReleased AppReleaseType = "Released"
)
type AppConfig struct {
ConfFile string `json:"conf,omitempty"`
Stanza string `json:"stanza,omitempty"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
}
type App struct {
ID int32 `json:"id,omitempty"`
Targets []string `json:"targets,omitempty"`
Version string `json:"version,omitempty"`
Name string `json:"name,omitempty"`
PurgeConf map[string][]string `json:"purgeconf,omitempty"`
PurgeDir map[string][]string `json:"purgedir,omitempty"`
Config []AppConfig `json:"config,omitempty"`
Ensure string `json:"ensure,omitempty"`
SkipCompatibilityCheck bool `json:"skipCompatibilityCheck,omitempty"`
ExtractPath string `json:"extractPath,omitempty"`
Context *string `json:"context,omitempty"`
ReleaseType AppReleaseType `json:"releaseType,omitempty"`
}
type ThisThing struct {
InlineThing `json:",inline"`
AField string `json:"aField,omitempty"`
BField map[string][]string `json:"bField,omitempty"`
CField NestedThing `json:"cField,omitempty"`
DField int `json:"dField,omitempty"`
EField int8 `json:"eField,omitempty"`
FField int16 `json:"fField,omitempty"`
GField int32 `json:"gField,omitempty"`
HField int64 `json:"hField,omitempty"`
IField bool `json:"iField,omitempty"`
Time Time `json:"time,omitempty"`
PointerThing *PointerThing `json:"pointerThing,omitempty"`
RepeatedField []RepeatedThing `json:"repeatedField,omitempty"`
Apps []App `json:"apps,omitempty"`
}
/*
Open Search Types
===================
* Null
* Boolean
* Float
* Double
* Integer
* Object
* Array
* Text
* Keyword
* Date Detection String
* Numeric Detection String
{
"mappings": {
"properties": {
"year": { "type": "text" },
"age": {"type": "integer"},
"director": {"type": "text"}
}
}
}
*/
func main() {
tt := &ThisThing{}
// tt.BuildMapping()
fmt.Println(string(tt.BuildMapping()))
}
func (u *ThisThing) BuildMapping() []byte {
value := reflect.ValueOf(*u)
t := value.Type()
return buildMapping(t, false, false)
}
func handleMappingKind(k reflect.Kind) []byte {
out := make([]byte, 0)
switch k {
case reflect.Map:
return append(out, []byte(`{"type": "object"}`)...)
case reflect.Bool:
return append(out, []byte(`{"type": "boolean"}`)...)
case reflect.Float32:
return append(out, []byte(`{"type": "float"}`)...)
case reflect.Float64:
return append(out, []byte(`{"type": "double"}`)...)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return append(out, []byte(`{"type": "integer"}`)...)
}
return append(out, []byte(`{"type": "text"}`)...)
}
func handleMappingType(t reflect.Type, i int, tag string) []byte {
var out []byte
// Handle custom types differently if needed
switch t.Field(i).Type {
case reflect.TypeOf(Time{}):
return append(out, []byte(`{"type": "text"}`)...)
}
switch t.Field(i).Type.Kind() {
case reflect.Slice:
if t.Field(i).Type.Elem().Kind() == reflect.Struct {
return append(out, buildMapping(t.Field(i).Type.Elem(), true, tag == ",inline")...)
}
return append(out, []byte(`{"type": "keyword"}`)...)
case reflect.Struct:
return append(out, buildMapping(t.Field(i).Type, true, tag == ",inline")...)
case reflect.Pointer:
if t.Field(i).Type.Elem().Kind() == reflect.Struct {
return append(out, buildMapping(t.Field(i).Type.Elem(), true, tag == ",inline")...)
}
return append(out, handleMappingKind(t.Field(i).Type.Elem().Kind())...)
}
return append(out, handleMappingKind(t.Field(i).Type.Kind())...)
}
func buildMapping(t reflect.Type, isRecursive bool, isInline bool) []byte {
var out []byte
if !isInline {
out = append(out, '{')
}
if !isInline && isRecursive {
out = append(out, []byte(`"type": "object",`)...)
}
if !isInline {
out = append(out, []byte(`"properties": {`)...)
}
r := regexp.MustCompile(`json:"(?P<tag>.*?)"`)
for i := 0; i < t.NumField(); i++ {
m := r.FindStringSubmatch(string(t.Field(i).Tag))
if len(m) == 0 {
continue
}
tag := strings.ReplaceAll(m[r.SubexpIndex("tag")], ",omitempty", "")
if tag != ",inline" {
out = append(out, fmt.Sprintf(`"%s": `, tag)...)
}
out = append(out, handleMappingType(t, i, tag)...)
if i < t.NumField()-1 {
out = append(out, ',')
}
}
if len(out) > 0 && out[len(out)-1] == 44 {
out = out[:len(out)-1]
}
if !isInline {
out = append(out, []byte("}}")...)
}
return out
}