// Package candidatejson provides strict JSON mechanics for D&D artifact // candidates before artifact-specific validation. package candidatejson import ( "bytes" "encoding/json" "fmt" "io" ) // EncodeCandidate encodes a typed candidate with an artifact-specific error // label. func EncodeCandidate[T any](label string, value T) ([]byte, error) { content, err := json.Marshal(value) if err != nil { return nil, fmt.Errorf("encode %s: %w", label, err) } return content, nil } // DecodeCandidate decodes exactly one typed JSON value while rejecting unknown // fields and trailing values with an artifact-specific error label. func DecodeCandidate[T any](label string, content []byte) (T, error) { decoder := json.NewDecoder(bytes.NewReader(content)) decoder.DisallowUnknownFields() var value T if err := decoder.Decode(&value); err != nil { var zero T return zero, fmt.Errorf("decode %s: %w", label, err) } var trailing any if err := decoder.Decode(&trailing); err != io.EOF { var zero T return zero, fmt.Errorf("decode %s: multiple JSON values", label) } return value, nil }