fish2018/pansou · error
[ ] JSON解析失败
Error message
[%s] JSON解析失败: %w
What it means
ikantv plugin's doSearch throws this when json.Unmarshal(body, &apiResp) fails to decode the response into apiResponse. The body is not valid JSON or its field types conflict with the apiResponse struct.
Solutions
- Dump/log the raw body on failure to see whether it is JSON, HTML, or empty.
- Compare the body against the apiResponse struct — update field names/types if the API schema changed.
- Check whether the API now requires different headers/cookies and update the request.
- Add content-type validation before unmarshalling to give a clearer error on HTML responses.
Example fix
// before
var apiResp apiResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
// after
var apiResp apiResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %s...: %w", p.Name(), string(body[:min(200, len(body))]), err)
} Defensive patterns
Strategy: type-guard
Validate before calling
// verify the response is JSON before decoding
if !strings.Contains(resp.Header.Get("Content-Type"), "json") {
return fmt.Errorf("ikantv returned non-JSON content-type: %q", resp.Header.Get("Content-Type"))
} Type guard
func isJSONBody(b []byte) bool {
t := bytes.TrimSpace(b)
return len(t) > 0 && (t[0] == '{' || t[0] == '[')
} Try / catch
results, err := p.doSearch(ctx, keyword)
if err != nil && strings.Contains(err.Error(), "JSON解析失败") {
// log the raw body once to determine HTML-vs-schema drift, then update the struct
log.Printf("ikantv response not JSON — check API contract: %v", err)
} Prevention
- Dump raw bodies on decode failure to distinguish challenge pages from schema drift
- Validate content-type before unmarshalling
- Keep apiResponse struct aligned with the live API (field names and JSON types)
- Re-run integration tests against the upstream after site updates
When it happens
Trigger: The 200-status body is HTML (anti-bot interstitial), empty, truncated, or JSON with mismatched types (e.g. code delivered as string not number), so unmarshalling into apiResponse errors.
Common situations: Site changed its API shape or now serves a challenge page instead of JSON; CDN/WAF injects HTML; an upstream outage returns an error page with status 200.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c84a4c7fdbc3aa90.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/ikantv/ikantv.go:100
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应失败: %w", p.Name(), err)
}
var apiResp apiResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
if apiResp.Code != 0 {
return nil, fmt.Errorf("[%s] API错误: %s", p.Name(), apiResp.Message)
}
results := convertResults(apiResp.Data)
return plugin.FilterResultsByKeyword(results, keyword), nil
}
func convertResults(items []apiItem) []model.SearchResult {
results := make([]model.SearchResult, 0, len(items))
for _, item := range items {
result, ok := convertResult(item)
if !ok {
continue
}
results = append(results, result)
}View on GitHub (pinned to beaa561337)