// +build jq package gateway // #cgo LDFLAGS: -ljq // #include // #include import "C" import ( "errors" "fmt" "reflect" ) // JQ type stores a JQ vm // Must be protected with mutex in threaded environment type JQ struct { state *C.jq_state } // NewJQ creates a bew JQ vm. program is the actual JQ filter to compile func NewJQ(program string) (*JQ, error) { state := C.jq_init() if rc := C.jq_compile(state, C.CString(program)); rc == 0 { C.jq_teardown(&state) return nil, errors.New("Unable to compile jq filter") } return &JQ{state}, nil } // Handle applies the compiled filter ton the value func (jq *JQ) Handle(value interface{}) (interface{}, error) { jv := goToJv(value) if !isValid(jv) { C.jv_free(jv) return nil, errors.New("Invalid JSON") } C.jq_start(jq.state, jv, 0) jvResult := C.jq_next(jq.state) if !isValid(jvResult) { C.jv_free(jvResult) return nil, errors.New("Error while applying JQ transformation XXX: Get the error message from jv_result") } result := jvToGo(jvResult) C.jv_free(jvResult) return result, nil } func isValid(jv C.jv) bool { return C.jv_is_valid(jv) != 0 } func goToJv(v interface{}) C.jv { if v == nil { return C.jv_null() } value := reflect.Indirect(reflect.ValueOf(v)) switch value.Type().Kind() { case reflect.Bool: if value.Bool() { return C.jv_true() } else { return C.jv_false() } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return C.jv_number(C.double(value.Int())) // TODO reflect.Uintptr? case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return C.jv_number(C.double(value.Uint())) case reflect.Float32, reflect.Float64: return C.jv_number(C.double(value.Float())) case reflect.String: return C.jv_string(C.CString(value.String())) case reflect.Array, reflect.Slice: n := value.Len() arr := C.jv_array_sized(C.int(n)) for i := 0; i < n; i++ { item := goToJv(value.Index(i).Interface()) arr = C.jv_array_set(C.jv_copy(arr), C.int(i), item) } return arr case reflect.Map: // TODO assert key is string? object := C.jv_object() for _, k := range value.MapKeys() { key := goToJv(k.Interface()) mapValue := goToJv(value.MapIndex(k).Interface()) object = C.jv_object_set(object, key, mapValue) } return object } msg := fmt.Sprintf("unknown type for: %v", value.Interface()) return C.jv_invalid_with_msg(C.jv_string(C.CString(msg))) } func jvToGo(value C.jv) interface{} { switch C.jv_get_kind(value) { case C.JV_KIND_INVALID: return errors.New("invalid") case C.JV_KIND_NULL: return nil case C.JV_KIND_FALSE: return false case C.JV_KIND_TRUE: return true case C.JV_KIND_NUMBER: number := C.jv_number_value(value) if C.jv_is_integer(value) == 0 { return float64(number) } else { return int(number) } case C.JV_KIND_STRING: return C.GoString(C.jv_string_value(value)) case C.JV_KIND_ARRAY: length := C.jv_array_length(C.jv_copy(value)) arr := make([]interface{}, length) for i := range arr { arr[i] = jvToGo(C.jv_array_get(C.jv_copy(value), C.int(i))) } return arr case C.JV_KIND_OBJECT: result := make(map[string]interface{}) var k, v C.jv for jv_i := C.jv_object_iter(value); C.jv_object_iter_valid(value, jv_i) != 0; jv_i = C.jv_object_iter_next(value, jv_i) { k = C.jv_object_iter_key(value, jv_i) v = C.jv_object_iter_value(value, jv_i) result[C.GoString(C.jv_string_value(k))] = jvToGo(v) } return result default: return errors.New("unknown type") } }