fish2018/pansou · error
[ ] JSON解析失败
Error message
[%s] JSON解析失败: %w
What it means
After reading the body, searchImpl unmarshals it into the searchResponse struct with json.Unmarshal; failure produces this wrapped error. It means the 200 response body was not the expected JSON shape — typically an HTML anti-bot/interstitial page or an envelope whose schema changed.
Solutions
- Log the wrapped %w error and a snippet of body to see what was actually returned
- Check whether the body is HTML (WAF page) and update Origin/Referer/User-Agent or add cookie handling to pass the challenge
- Diff the live API JSON against the searchResponse struct and update field names/types/json tags
- Use json.Decoder and tolerate schema drift by unmarshaling into json.RawMessage for volatile fields
Example fix
// before
var apiResp searchResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
// after
var apiResp searchResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败(响应: %.200s): %w", p.Name(), string(body), err)
} Defensive patterns
Strategy: validation
Validate before calling
trimmed := bytes.TrimSpace(body)
if len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') {
// not JSON (likely an HTML challenge page); skip unmarshal
return nil, fmt.Errorf("non-JSON response: %.100s", string(trimmed))
} Type guard
func looksLikeJSON(b []byte) bool {
t := bytes.TrimSpace(b)
return len(t) > 0 && (t[0] == '{' || t[0] == '[')
} Try / catch
if err != nil {
var synErr *json.SyntaxError
if errors.As(err, &synErr) {
log.Printf("bad JSON at offset %d, body head: %.200s", synErr.Offset, string(body))
}
return nil, err
} Prevention
- Check body starts with '{' or '[' before unmarshaling to catch HTML challenge pages
- Keep searchResponse struct json tags in sync with the live API (test against a recorded fixture)
- Never assume a 200 status means valid JSON — always validate shape
- Pin/verify the API version and update struct definitions after site changes
When it happens
Trigger: Response is valid HTTP 200 but not parseable into searchResponse: HTML challenge page, plain-text error, empty body, or API response fields renamed/retyped after a site update so types no longer match.
Common situations: WAF serves a 200 'checking your browser' page; the API updated its JSON envelope (field renames, data changed from array to object); a CDN returns an HTML 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/18bd254a0a8f361a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/meitizy/meitizy.go:191
if err != nil {
return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("[%s] 搜索请求HTTP状态错误: %d", p.Name(), resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[%s] 读取响应体失败: %w", p.Name(), err)
}
// 解析JSON响应
var apiResp searchResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("[%s] JSON解析失败: %w", p.Name(), err)
}
// 转换为标准格式
results := p.convertToSearchResults(apiResp.Data)
// 关键词过滤(标准网盘插件需要过滤)
filteredResults := plugin.FilterResultsByKeyword(results, keyword)
return filteredResults, nil
}
// convertToSearchResults 将API响应转换为标准SearchResult格式
func (p *MeitizyPlugin) convertToSearchResults(items []apiItem) []model.SearchResult {
results := make([]model.SearchResult, 0, len(items))
for _, item := range items {
// Skip malformed or empty links returned by the API.
linkURL := strings.TrimSpace(item.Link)View on GitHub (pinned to beaa561337)