mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
Add claude code support
This commit is contained in:
555
internal/translator/claude/gemini/claude_gemini_response.go
Normal file
555
internal/translator/claude/gemini/claude_gemini_response.go
Normal file
@@ -0,0 +1,555 @@
|
||||
// Package gemini provides response translation functionality for Anthropic to Gemini API.
|
||||
// This package handles the conversion of Anthropic API responses into Gemini-compatible
|
||||
// JSON format, transforming streaming events and non-streaming responses into the format
|
||||
// expected by Gemini API clients. It supports both streaming and non-streaming modes,
|
||||
// handling text content, tool calls, and usage metadata appropriately.
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// ConvertAnthropicResponseToGeminiParams holds parameters for response conversion
|
||||
// It also carries minimal streaming state across calls to assemble tool_use input_json_delta.
|
||||
type ConvertAnthropicResponseToGeminiParams struct {
|
||||
Model string
|
||||
CreatedAt int64
|
||||
ResponseID string
|
||||
LastStorageOutput string
|
||||
IsStreaming bool
|
||||
|
||||
// Streaming state for tool_use assembly
|
||||
// Keyed by content_block index from Claude SSE events
|
||||
ToolUseNames map[int]string // function/tool name per block index
|
||||
ToolUseArgs map[int]*strings.Builder // accumulates partial_json across deltas
|
||||
}
|
||||
|
||||
// ConvertAnthropicResponseToGemini converts Anthropic streaming response format to Gemini format.
|
||||
// This function processes various Anthropic event types and transforms them into Gemini-compatible JSON responses.
|
||||
// It handles text content, tool calls, and usage metadata, outputting responses that match the Gemini API format.
|
||||
func ConvertAnthropicResponseToGemini(rawJSON []byte, param *ConvertAnthropicResponseToGeminiParams) []string {
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
eventType := root.Get("type").String()
|
||||
|
||||
// Base Gemini response template
|
||||
template := `{"candidates":[{"content":{"role":"model","parts":[]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"","createTime":"","responseId":""}`
|
||||
|
||||
// Set model version
|
||||
if param.Model != "" {
|
||||
// Map Claude model names back to Gemini model names
|
||||
template, _ = sjson.Set(template, "modelVersion", param.Model)
|
||||
}
|
||||
|
||||
// Set response ID and creation time
|
||||
if param.ResponseID != "" {
|
||||
template, _ = sjson.Set(template, "responseId", param.ResponseID)
|
||||
}
|
||||
|
||||
// Set creation time to current time if not provided
|
||||
if param.CreatedAt == 0 {
|
||||
param.CreatedAt = time.Now().Unix()
|
||||
}
|
||||
template, _ = sjson.Set(template, "createTime", time.Unix(param.CreatedAt, 0).Format(time.RFC3339Nano))
|
||||
|
||||
switch eventType {
|
||||
case "message_start":
|
||||
// Initialize response with message metadata
|
||||
if message := root.Get("message"); message.Exists() {
|
||||
param.ResponseID = message.Get("id").String()
|
||||
param.Model = message.Get("model").String()
|
||||
template, _ = sjson.Set(template, "responseId", param.ResponseID)
|
||||
template, _ = sjson.Set(template, "modelVersion", param.Model)
|
||||
}
|
||||
return []string{template}
|
||||
|
||||
case "content_block_start":
|
||||
// Start of a content block - record tool_use name by index for functionCall
|
||||
if cb := root.Get("content_block"); cb.Exists() {
|
||||
if cb.Get("type").String() == "tool_use" {
|
||||
idx := int(root.Get("index").Int())
|
||||
if param.ToolUseNames == nil {
|
||||
param.ToolUseNames = map[int]string{}
|
||||
}
|
||||
if name := cb.Get("name"); name.Exists() {
|
||||
param.ToolUseNames[idx] = name.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return []string{template}
|
||||
|
||||
case "content_block_delta":
|
||||
// Handle content delta (text, thinking, or tool use)
|
||||
if delta := root.Get("delta"); delta.Exists() {
|
||||
deltaType := delta.Get("type").String()
|
||||
|
||||
switch deltaType {
|
||||
case "text_delta":
|
||||
// Regular text content delta
|
||||
if text := delta.Get("text"); text.Exists() && text.String() != "" {
|
||||
textPart := `{"text":""}`
|
||||
textPart, _ = sjson.Set(textPart, "text", text.String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", textPart)
|
||||
}
|
||||
case "thinking_delta":
|
||||
// Thinking/reasoning content delta
|
||||
if text := delta.Get("text"); text.Exists() && text.String() != "" {
|
||||
thinkingPart := `{"thought":true,"text":""}`
|
||||
thinkingPart, _ = sjson.Set(thinkingPart, "text", text.String())
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", thinkingPart)
|
||||
}
|
||||
case "input_json_delta":
|
||||
// Tool use input delta - accumulate partial_json by index for later assembly at content_block_stop
|
||||
idx := int(root.Get("index").Int())
|
||||
if param.ToolUseArgs == nil {
|
||||
param.ToolUseArgs = map[int]*strings.Builder{}
|
||||
}
|
||||
b, ok := param.ToolUseArgs[idx]
|
||||
if !ok || b == nil {
|
||||
bb := &strings.Builder{}
|
||||
param.ToolUseArgs[idx] = bb
|
||||
b = bb
|
||||
}
|
||||
if pj := delta.Get("partial_json"); pj.Exists() {
|
||||
b.WriteString(pj.String())
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
return []string{template}
|
||||
|
||||
case "content_block_stop":
|
||||
// End of content block - finalize tool calls if any
|
||||
idx := int(root.Get("index").Int())
|
||||
// Claude's content_block_stop often doesn't include content_block payload (see docs/response-claude.txt)
|
||||
// So we finalize using accumulated state captured during content_block_start and input_json_delta.
|
||||
name := ""
|
||||
if param.ToolUseNames != nil {
|
||||
name = param.ToolUseNames[idx]
|
||||
}
|
||||
var argsTrim string
|
||||
if param.ToolUseArgs != nil {
|
||||
if b := param.ToolUseArgs[idx]; b != nil {
|
||||
argsTrim = strings.TrimSpace(b.String())
|
||||
}
|
||||
}
|
||||
if name != "" || argsTrim != "" {
|
||||
functionCall := `{"functionCall":{"name":"","args":{}}}`
|
||||
if name != "" {
|
||||
functionCall, _ = sjson.Set(functionCall, "functionCall.name", name)
|
||||
}
|
||||
if argsTrim != "" {
|
||||
functionCall, _ = sjson.SetRaw(functionCall, "functionCall.args", argsTrim)
|
||||
}
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", functionCall)
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
param.LastStorageOutput = template
|
||||
// cleanup used state for this index
|
||||
if param.ToolUseArgs != nil {
|
||||
delete(param.ToolUseArgs, idx)
|
||||
}
|
||||
if param.ToolUseNames != nil {
|
||||
delete(param.ToolUseNames, idx)
|
||||
}
|
||||
return []string{template}
|
||||
}
|
||||
return []string{}
|
||||
|
||||
case "message_delta":
|
||||
// Handle message-level changes (like stop reason)
|
||||
if delta := root.Get("delta"); delta.Exists() {
|
||||
if stopReason := delta.Get("stop_reason"); stopReason.Exists() {
|
||||
switch stopReason.String() {
|
||||
case "end_turn":
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
case "tool_use":
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
case "max_tokens":
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "MAX_TOKENS")
|
||||
case "stop_sequence":
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
default:
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if usage := root.Get("usage"); usage.Exists() {
|
||||
// Basic token counts
|
||||
inputTokens := usage.Get("input_tokens").Int()
|
||||
outputTokens := usage.Get("output_tokens").Int()
|
||||
|
||||
// Set basic usage metadata according to Gemini API specification
|
||||
template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", inputTokens)
|
||||
template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", outputTokens)
|
||||
template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", inputTokens+outputTokens)
|
||||
|
||||
// Add cache-related token counts if present (Anthropic API cache fields)
|
||||
if cacheCreationTokens := usage.Get("cache_creation_input_tokens"); cacheCreationTokens.Exists() {
|
||||
template, _ = sjson.Set(template, "usageMetadata.cachedContentTokenCount", cacheCreationTokens.Int())
|
||||
}
|
||||
if cacheReadTokens := usage.Get("cache_read_input_tokens"); cacheReadTokens.Exists() {
|
||||
// Add cache read tokens to cached content count
|
||||
existingCacheTokens := usage.Get("cache_creation_input_tokens").Int()
|
||||
totalCacheTokens := existingCacheTokens + cacheReadTokens.Int()
|
||||
template, _ = sjson.Set(template, "usageMetadata.cachedContentTokenCount", totalCacheTokens)
|
||||
}
|
||||
|
||||
// Add thinking tokens if present (for models with reasoning capabilities)
|
||||
if thinkingTokens := usage.Get("thinking_tokens"); thinkingTokens.Exists() {
|
||||
template, _ = sjson.Set(template, "usageMetadata.thoughtsTokenCount", thinkingTokens.Int())
|
||||
}
|
||||
|
||||
// Set traffic type (required by Gemini API)
|
||||
template, _ = sjson.Set(template, "usageMetadata.trafficType", "PROVISIONED_THROUGHPUT")
|
||||
}
|
||||
template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP")
|
||||
|
||||
return []string{template}
|
||||
case "message_stop":
|
||||
// Final message with usage information
|
||||
return []string{}
|
||||
case "error":
|
||||
// Handle error responses
|
||||
errorMsg := root.Get("error.message").String()
|
||||
if errorMsg == "" {
|
||||
errorMsg = "Unknown error occurred"
|
||||
}
|
||||
|
||||
// Create error response in Gemini format
|
||||
errorResponse := `{"error":{"code":400,"message":"","status":"INVALID_ARGUMENT"}}`
|
||||
errorResponse, _ = sjson.Set(errorResponse, "error.message", errorMsg)
|
||||
return []string{errorResponse}
|
||||
|
||||
default:
|
||||
// Unknown event type, return empty
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertAnthropicResponseToGeminiNonStream converts Anthropic streaming events to a single Gemini non-streaming response.
|
||||
// This function processes multiple Anthropic streaming events and aggregates them into a complete
|
||||
// Gemini-compatible JSON response that includes all content parts (including thinking/reasoning),
|
||||
// function calls, and usage metadata. It simulates the streaming process internally but returns
|
||||
// a single consolidated response.
|
||||
func ConvertAnthropicResponseToGeminiNonStream(streamingEvents [][]byte, model string) string {
|
||||
// Base Gemini response template for non-streaming
|
||||
template := `{"candidates":[{"content":{"role":"model","parts":[]},"finishReason":"STOP"}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"","createTime":"","responseId":""}`
|
||||
|
||||
// Set model version
|
||||
template, _ = sjson.Set(template, "modelVersion", model)
|
||||
|
||||
// Initialize parameters for streaming conversion
|
||||
param := &ConvertAnthropicResponseToGeminiParams{
|
||||
Model: model,
|
||||
IsStreaming: false,
|
||||
}
|
||||
|
||||
// Process each streaming event and collect parts
|
||||
var allParts []interface{}
|
||||
var finalUsage map[string]interface{}
|
||||
var responseID string
|
||||
var createdAt int64
|
||||
|
||||
for _, eventData := range streamingEvents {
|
||||
if len(eventData) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
root := gjson.ParseBytes(eventData)
|
||||
eventType := root.Get("type").String()
|
||||
|
||||
switch eventType {
|
||||
case "message_start":
|
||||
// Extract response metadata
|
||||
if message := root.Get("message"); message.Exists() {
|
||||
responseID = message.Get("id").String()
|
||||
param.ResponseID = responseID
|
||||
param.Model = message.Get("model").String()
|
||||
|
||||
// Set creation time to current time if not provided
|
||||
createdAt = time.Now().Unix()
|
||||
param.CreatedAt = createdAt
|
||||
}
|
||||
|
||||
case "content_block_start":
|
||||
// Prepare for content block; record tool_use name by index for later functionCall assembly
|
||||
idx := int(root.Get("index").Int())
|
||||
if cb := root.Get("content_block"); cb.Exists() {
|
||||
if cb.Get("type").String() == "tool_use" {
|
||||
if param.ToolUseNames == nil {
|
||||
param.ToolUseNames = map[int]string{}
|
||||
}
|
||||
if name := cb.Get("name"); name.Exists() {
|
||||
param.ToolUseNames[idx] = name.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
||||
case "content_block_delta":
|
||||
// Handle content delta (text, thinking, or tool input)
|
||||
if delta := root.Get("delta"); delta.Exists() {
|
||||
deltaType := delta.Get("type").String()
|
||||
switch deltaType {
|
||||
case "text_delta":
|
||||
if text := delta.Get("text"); text.Exists() && text.String() != "" {
|
||||
partJSON := `{"text":""}`
|
||||
partJSON, _ = sjson.Set(partJSON, "text", text.String())
|
||||
part := gjson.Parse(partJSON).Value().(map[string]interface{})
|
||||
allParts = append(allParts, part)
|
||||
}
|
||||
case "thinking_delta":
|
||||
if text := delta.Get("text"); text.Exists() && text.String() != "" {
|
||||
partJSON := `{"thought":true,"text":""}`
|
||||
partJSON, _ = sjson.Set(partJSON, "text", text.String())
|
||||
part := gjson.Parse(partJSON).Value().(map[string]interface{})
|
||||
allParts = append(allParts, part)
|
||||
}
|
||||
case "input_json_delta":
|
||||
// accumulate args partial_json for this index
|
||||
idx := int(root.Get("index").Int())
|
||||
if param.ToolUseArgs == nil {
|
||||
param.ToolUseArgs = map[int]*strings.Builder{}
|
||||
}
|
||||
if _, ok := param.ToolUseArgs[idx]; !ok || param.ToolUseArgs[idx] == nil {
|
||||
param.ToolUseArgs[idx] = &strings.Builder{}
|
||||
}
|
||||
if pj := delta.Get("partial_json"); pj.Exists() {
|
||||
param.ToolUseArgs[idx].WriteString(pj.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "content_block_stop":
|
||||
// Handle tool use completion
|
||||
idx := int(root.Get("index").Int())
|
||||
// Claude's content_block_stop often doesn't include content_block payload (see docs/response-claude.txt)
|
||||
// So we finalize using accumulated state captured during content_block_start and input_json_delta.
|
||||
name := ""
|
||||
if param.ToolUseNames != nil {
|
||||
name = param.ToolUseNames[idx]
|
||||
}
|
||||
var argsTrim string
|
||||
if param.ToolUseArgs != nil {
|
||||
if b := param.ToolUseArgs[idx]; b != nil {
|
||||
argsTrim = strings.TrimSpace(b.String())
|
||||
}
|
||||
}
|
||||
if name != "" || argsTrim != "" {
|
||||
functionCallJSON := `{"functionCall":{"name":"","args":{}}}`
|
||||
if name != "" {
|
||||
functionCallJSON, _ = sjson.Set(functionCallJSON, "functionCall.name", name)
|
||||
}
|
||||
if argsTrim != "" {
|
||||
functionCallJSON, _ = sjson.SetRaw(functionCallJSON, "functionCall.args", argsTrim)
|
||||
}
|
||||
// Parse back to interface{} for allParts
|
||||
functionCall := gjson.Parse(functionCallJSON).Value().(map[string]interface{})
|
||||
allParts = append(allParts, functionCall)
|
||||
// cleanup used state for this index
|
||||
if param.ToolUseArgs != nil {
|
||||
delete(param.ToolUseArgs, idx)
|
||||
}
|
||||
if param.ToolUseNames != nil {
|
||||
delete(param.ToolUseNames, idx)
|
||||
}
|
||||
}
|
||||
|
||||
case "message_delta":
|
||||
// Extract final usage information using sjson
|
||||
if usage := root.Get("usage"); usage.Exists() {
|
||||
usageJSON := `{}`
|
||||
|
||||
// Basic token counts
|
||||
inputTokens := usage.Get("input_tokens").Int()
|
||||
outputTokens := usage.Get("output_tokens").Int()
|
||||
|
||||
// Set basic usage metadata according to Gemini API specification
|
||||
usageJSON, _ = sjson.Set(usageJSON, "promptTokenCount", inputTokens)
|
||||
usageJSON, _ = sjson.Set(usageJSON, "candidatesTokenCount", outputTokens)
|
||||
usageJSON, _ = sjson.Set(usageJSON, "totalTokenCount", inputTokens+outputTokens)
|
||||
|
||||
// Add cache-related token counts if present (Anthropic API cache fields)
|
||||
if cacheCreationTokens := usage.Get("cache_creation_input_tokens"); cacheCreationTokens.Exists() {
|
||||
usageJSON, _ = sjson.Set(usageJSON, "cachedContentTokenCount", cacheCreationTokens.Int())
|
||||
}
|
||||
if cacheReadTokens := usage.Get("cache_read_input_tokens"); cacheReadTokens.Exists() {
|
||||
// Add cache read tokens to cached content count
|
||||
existingCacheTokens := usage.Get("cache_creation_input_tokens").Int()
|
||||
totalCacheTokens := existingCacheTokens + cacheReadTokens.Int()
|
||||
usageJSON, _ = sjson.Set(usageJSON, "cachedContentTokenCount", totalCacheTokens)
|
||||
}
|
||||
|
||||
// Add thinking tokens if present (for models with reasoning capabilities)
|
||||
if thinkingTokens := usage.Get("thinking_tokens"); thinkingTokens.Exists() {
|
||||
usageJSON, _ = sjson.Set(usageJSON, "thoughtsTokenCount", thinkingTokens.Int())
|
||||
}
|
||||
|
||||
// Set traffic type (required by Gemini API)
|
||||
usageJSON, _ = sjson.Set(usageJSON, "trafficType", "PROVISIONED_THROUGHPUT")
|
||||
|
||||
// Convert to map[string]interface{} using gjson
|
||||
finalUsage = gjson.Parse(usageJSON).Value().(map[string]interface{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set response metadata
|
||||
if responseID != "" {
|
||||
template, _ = sjson.Set(template, "responseId", responseID)
|
||||
}
|
||||
if createdAt > 0 {
|
||||
template, _ = sjson.Set(template, "createTime", time.Unix(createdAt, 0).Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
// Consolidate consecutive text parts and thinking parts
|
||||
consolidatedParts := consolidateParts(allParts)
|
||||
|
||||
// Set the consolidated parts array
|
||||
if len(consolidatedParts) > 0 {
|
||||
template, _ = sjson.SetRaw(template, "candidates.0.content.parts", convertToJSONString(consolidatedParts))
|
||||
}
|
||||
|
||||
// Set usage metadata
|
||||
if finalUsage != nil {
|
||||
template, _ = sjson.SetRaw(template, "usageMetadata", convertToJSONString(finalUsage))
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
// consolidateParts merges consecutive text parts and thinking parts to create a cleaner response
|
||||
func consolidateParts(parts []interface{}) []interface{} {
|
||||
if len(parts) == 0 {
|
||||
return parts
|
||||
}
|
||||
|
||||
var consolidated []interface{}
|
||||
var currentTextPart strings.Builder
|
||||
var currentThoughtPart strings.Builder
|
||||
var hasText, hasThought bool
|
||||
|
||||
flushText := func() {
|
||||
if hasText && currentTextPart.Len() > 0 {
|
||||
textPartJSON := `{"text":""}`
|
||||
textPartJSON, _ = sjson.Set(textPartJSON, "text", currentTextPart.String())
|
||||
textPart := gjson.Parse(textPartJSON).Value().(map[string]interface{})
|
||||
consolidated = append(consolidated, textPart)
|
||||
currentTextPart.Reset()
|
||||
hasText = false
|
||||
}
|
||||
}
|
||||
|
||||
flushThought := func() {
|
||||
if hasThought && currentThoughtPart.Len() > 0 {
|
||||
thoughtPartJSON := `{"thought":true,"text":""}`
|
||||
thoughtPartJSON, _ = sjson.Set(thoughtPartJSON, "text", currentThoughtPart.String())
|
||||
thoughtPart := gjson.Parse(thoughtPartJSON).Value().(map[string]interface{})
|
||||
consolidated = append(consolidated, thoughtPart)
|
||||
currentThoughtPart.Reset()
|
||||
hasThought = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, part := range parts {
|
||||
partMap, ok := part.(map[string]interface{})
|
||||
if !ok {
|
||||
// Flush any pending parts and add this non-text part
|
||||
flushText()
|
||||
flushThought()
|
||||
consolidated = append(consolidated, part)
|
||||
continue
|
||||
}
|
||||
|
||||
if thought, isThought := partMap["thought"]; isThought && thought == true {
|
||||
// This is a thinking part
|
||||
flushText() // Flush any pending text first
|
||||
|
||||
if text, hasTextContent := partMap["text"].(string); hasTextContent {
|
||||
currentThoughtPart.WriteString(text)
|
||||
hasThought = true
|
||||
}
|
||||
} else if text, hasTextContent := partMap["text"].(string); hasTextContent {
|
||||
// This is a regular text part
|
||||
flushThought() // Flush any pending thought first
|
||||
|
||||
currentTextPart.WriteString(text)
|
||||
hasText = true
|
||||
} else {
|
||||
// This is some other type of part (like function call)
|
||||
flushText()
|
||||
flushThought()
|
||||
consolidated = append(consolidated, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining parts
|
||||
flushThought() // Flush thought first to maintain order
|
||||
flushText()
|
||||
|
||||
return consolidated
|
||||
}
|
||||
|
||||
// convertToJSONString converts interface{} to JSON string using sjson/gjson
|
||||
func convertToJSONString(v interface{}) string {
|
||||
switch val := v.(type) {
|
||||
case []interface{}:
|
||||
return convertArrayToJSON(val)
|
||||
case map[string]interface{}:
|
||||
return convertMapToJSON(val)
|
||||
default:
|
||||
// For simple types, create a temporary JSON and extract the value
|
||||
temp := `{"temp":null}`
|
||||
temp, _ = sjson.Set(temp, "temp", val)
|
||||
return gjson.Get(temp, "temp").Raw
|
||||
}
|
||||
}
|
||||
|
||||
// convertArrayToJSON converts []interface{} to JSON array string
|
||||
func convertArrayToJSON(arr []interface{}) string {
|
||||
result := "[]"
|
||||
for _, item := range arr {
|
||||
switch itemData := item.(type) {
|
||||
case map[string]interface{}:
|
||||
itemJSON := convertMapToJSON(itemData)
|
||||
result, _ = sjson.SetRaw(result, "-1", itemJSON)
|
||||
case string:
|
||||
result, _ = sjson.Set(result, "-1", itemData)
|
||||
case bool:
|
||||
result, _ = sjson.Set(result, "-1", itemData)
|
||||
case float64, int, int64:
|
||||
result, _ = sjson.Set(result, "-1", itemData)
|
||||
default:
|
||||
result, _ = sjson.Set(result, "-1", itemData)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// convertMapToJSON converts map[string]interface{} to JSON object string
|
||||
func convertMapToJSON(m map[string]interface{}) string {
|
||||
result := "{}"
|
||||
for key, value := range m {
|
||||
switch val := value.(type) {
|
||||
case map[string]interface{}:
|
||||
nestedJSON := convertMapToJSON(val)
|
||||
result, _ = sjson.SetRaw(result, key, nestedJSON)
|
||||
case []interface{}:
|
||||
arrayJSON := convertArrayToJSON(val)
|
||||
result, _ = sjson.SetRaw(result, key, arrayJSON)
|
||||
case string:
|
||||
result, _ = sjson.Set(result, key, val)
|
||||
case bool:
|
||||
result, _ = sjson.Set(result, key, val)
|
||||
case float64, int, int64:
|
||||
result, _ = sjson.Set(result, key, val)
|
||||
default:
|
||||
result, _ = sjson.Set(result, key, val)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user