package transcript import ( "bytes" "encoding/json" "fmt" "io" "strconv" "strings" ) type transcript struct { Metadata map[string]any `json:"metadata"` Segments []segment `json:"segments"` } type segment struct { ID int `json:"id"` Start json.Number `json:"start"` End json.Number `json:"end"` Speaker string `json:"speaker"` Text string `json:"text"` } func decodeTranscript(raw []byte) (transcript, error) { var fields map[string]json.RawMessage if err := decodeJSON(raw, &fields); err != nil { return transcript{}, err } if fields == nil { return transcript{}, fmt.Errorf("top-level value must be an object") } metadataRaw, ok := fields["metadata"] if !ok { return transcript{}, fmt.Errorf("metadata is required") } var metadata map[string]any if err := decodeJSON(metadataRaw, &metadata); err != nil { return transcript{}, fmt.Errorf("metadata must be an object: %w", err) } if metadata == nil { return transcript{}, fmt.Errorf("metadata must be an object") } segmentsRaw, ok := fields["segments"] if !ok { return transcript{}, fmt.Errorf("segments are required") } var segmentValues []json.RawMessage if err := decodeJSON(segmentsRaw, &segmentValues); err != nil { return transcript{}, fmt.Errorf("segments must be an array: %w", err) } if segmentValues == nil { return transcript{}, fmt.Errorf("segments must be an array") } segments := make([]segment, 0, len(segmentValues)) for i, rawSegment := range segmentValues { segment, err := decodeSegment(rawSegment, i) if err != nil { return transcript{}, err } segments = append(segments, segment) } return transcript{ Metadata: metadata, Segments: segments, }, nil } func decodeSegment(raw []byte, index int) (segment, error) { var fields map[string]json.RawMessage if err := decodeJSON(raw, &fields); err != nil { return segment{}, fmt.Errorf("segment[%d] must be an object: %w", index, err) } if fields == nil { return segment{}, fmt.Errorf("segment[%d] must be an object", index) } var decoded segment if err := decodeOptionalSegmentID(fields, "id", &decoded.ID); err != nil { return segment{}, fmt.Errorf("segment[%d] id must be a positive integer string or number: %w", index, err) } if err := decodeOptionalNumber(fields, "start", &decoded.Start); err != nil { return segment{}, fmt.Errorf("segment[%d] start must be a number: %w", index, err) } if err := decodeOptionalNumber(fields, "end", &decoded.End); err != nil { return segment{}, fmt.Errorf("segment[%d] end must be a number: %w", index, err) } if err := decodeOptionalString(fields, "speaker", &decoded.Speaker); err != nil { return segment{}, fmt.Errorf("segment[%d] speaker must be a string: %w", index, err) } if err := decodeOptionalString(fields, "text", &decoded.Text); err != nil { return segment{}, fmt.Errorf("segment[%d] text must be a string: %w", index, err) } return decoded, nil } func decodeOptionalString(fields map[string]json.RawMessage, key string, out *string) error { raw, ok := fields[key] if !ok { return nil } return decodeJSON(raw, out) } func decodeOptionalSegmentID(fields map[string]json.RawMessage, key string, out *int) error { raw, ok := fields[key] if !ok { return nil } var text string if err := decodeJSON(raw, &text); err == nil { parsed, err := parsePositiveInt(text) if err != nil { return err } *out = parsed return nil } var number json.Number if err := decodeJSON(raw, &number); err == nil { parsed, err := parsePositiveInt(number.String()) if err != nil { return err } *out = parsed return nil } return fmt.Errorf("must be a positive integer string or number") } func parsePositiveInt(value string) (int, error) { trimmed := strings.TrimSpace(value) if trimmed == "" { return 0, fmt.Errorf("must not be empty") } if trimmed != value { return 0, fmt.Errorf("must not contain leading or trailing whitespace") } parsed, err := strconv.Atoi(value) if err != nil { return 0, fmt.Errorf("must be an integer") } if parsed <= 0 { return 0, fmt.Errorf("must be positive") } if strconv.Itoa(parsed) != value { return 0, fmt.Errorf("must be a canonical positive integer") } return parsed, nil } func decodeOptionalNumber(fields map[string]json.RawMessage, key string, out *json.Number) error { raw, ok := fields[key] if !ok { return nil } return decodeJSON(raw, out) } func decodeJSON(raw []byte, out any) error { decoder := json.NewDecoder(bytes.NewReader(raw)) decoder.UseNumber() if err := decoder.Decode(out); err != nil { return err } if err := decoder.Decode(&struct{}{}); err != io.EOF { if err == nil { return fmt.Errorf("unexpected trailing JSON value") } return err } return nil }