// Package contract 内部 JSON 序列化辅助(与契约逻辑分离,保持 contract.go 职责单一)。 package contract import "encoding/json" // jsonMarshal 统一带缩进关闭的紧凑序列化(HTML 转义开启与标准库默认一致)。 func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) } // UnmarshalJSON 从平铺 wire 还原 Data。缺此方法时 encoding/json 只填顶层字段, // Data 恒为 nil;再 Marshal 会丢掉 results/markdown(gateway 缓存命中改写即踩中)。 func (e *Envelope) UnmarshalJSON(b []byte) error { var top envelopeTop if err := json.Unmarshal(b, &top); err != nil { return err } e.OK = top.OK e.Kind = top.Kind e.RequestID = top.RequestID e.TookMs = top.TookMs e.Usage = top.Usage e.Provenance = top.Provenance e.Error = top.Error switch top.Kind { case "search": var w searchWire if err := json.Unmarshal(b, &w); err != nil { return err } p := &SearchPayload{Query: w.Query, Answer: w.Answer, Results: w.Results} p.EnsureEmptySlice() e.Data = p case "read": var w readWire if err := json.Unmarshal(b, &w); err != nil { return err } p := readPayloadFromWire(w) p.EnsureEmptySlice() e.Data = p } return nil } func readPayloadFromWire(w readWire) *ReadPayload { p := &ReadPayload{ Description: w.Description, HTML: w.HTML, ScreenshotURL: w.ScreenshotURL, Extracted: w.Extracted, Links: w.Links, Images: w.Images, Warnings: w.Warnings, } if w.URL != nil { p.URL = *w.URL } if w.FinalURL != nil { p.FinalURL = *w.FinalURL } if w.Title != nil { p.Title = *w.Title } if w.Markdown != nil { p.Markdown = *w.Markdown } if w.Truncated != nil { p.Truncated = *w.Truncated } if w.CharCount != nil { p.CharCount = *w.CharCount } if w.Metadata != nil { p.Metadata = *w.Metadata } return p }