// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !goexperiment.jsonv2 || !go1.25

// Package jsontest contains functionality to assist in testing JSON.
package jsontest

import (
	"bytes"
	"embed"
	"errors"
	"io"
	"io/fs"
	"path"
	"slices"
	"strings"
	"sync"
	"time"

	"github.com/go-json-experiment/json/internal/zstd"
)

// Embed the testdata directory as a fs.FS because this package is imported
// by other packages such that the location of testdata may change relative
// to the working directory of the test itself.
//
//go:embed testdata/*.json.zst
var testdataFS embed.FS

type Entry struct {
	Name string
	Data func() []byte
	New  func() any // nil if there is no concrete type for this
}

func mustGet[T any](v T, err error) T {
	if err != nil {
		panic(err)
	}
	return v
}

// Data is a list of JSON testdata.
var Data = func() (entries []Entry) {
	fis := mustGet(fs.ReadDir(testdataFS, "testdata"))
	slices.SortFunc(fis, func(x, y fs.DirEntry) int { return strings.Compare(x.Name(), y.Name()) })
	for _, fi := range fis {
		var entry Entry

		// Convert snake_case file name to CamelCase.
		words := strings.Split(strings.TrimSuffix(fi.Name(), ".json.zst"), "_")
		for i := range words {
			words[i] = strings.Title(words[i])
		}
		entry.Name = strings.Join(words, "")

		// Lazily read and decompress the test data.
		entry.Data = sync.OnceValue(func() []byte {
			filePath := path.Join("testdata", fi.Name())
			b := mustGet(fs.ReadFile(testdataFS, filePath))
			zr := zstd.NewReader(bytes.NewReader(b))
			return mustGet(io.ReadAll(zr))
		})

		// Check whether there is a concrete type for this data.
		switch entry.Name {
		case "CanadaGeometry":
			entry.New = func() any { return new(canadaRoot) }
		case "CitmCatalog":
			entry.New = func() any { return new(citmRoot) }
		case "GolangSource":
			entry.New = func() any { return new(golangRoot) }
		case "StringEscaped":
			entry.New = func() any { return new(stringRoot) }
		case "StringUnicode":
			entry.New = func() any { return new(stringRoot) }
		case "SyntheaFhir":
			entry.New = func() any { return new(syntheaRoot) }
		case "TwitterStatus":
			entry.New = func() any { return new(twitterRoot) }
		}

		entries = append(entries, entry)
	}
	return entries
}()

type (
	canadaRoot struct {
		Type     string `json:"type"`
		Features []struct {
			Type       string `json:"type"`
			Properties struct {
				Name string `json:"name"`
			} `json:"properties"`
			Geometry struct {
				Type        string         `json:"type"`
				Coordinates [][][2]float64 `json:"coordinates"`
			} `json:"geometry"`
		} `json:"features"`
	}
)

type (
	citmRoot struct {
		AreaNames                map[int64]string `json:"areaNames"`
		AudienceSubCategoryNames map[int64]string `json:"audienceSubCategoryNames"`
		BlockNames               map[int64]string `json:"blockNames"`
		Events                   map[int64]struct {
			Description string `json:"description"`
			ID          int    `json:"id"`
			Logo        string `json:"logo"`
			Name        string `json:"name"`
			SubTopicIds []int  `json:"subTopicIds"`
			SubjectCode any    `json:"subjectCode"`
			Subtitle    any    `json:"subtitle"`
			TopicIds    []int  `json:"topicIds"`
		} `json:"events"`
		Performances []struct {
			EventID int `json:"eventId"`
			ID      int `json:"id"`
			Logo    any `json:"logo"`
			Name    any `json:"name"`
			Prices  []struct {
				Amount                int   `json:"amount"`
				AudienceSubCategoryID int64 `json:"audienceSubCategoryId"`
				SeatCategoryID        int64 `json:"seatCategoryId"`
			} `json:"prices"`
			SeatCategories []struct {
				Areas []struct {
					AreaID   int   `json:"areaId"`
					BlockIds []any `json:"blockIds"`
				} `json:"areas"`
				SeatCategoryID int `json:"seatCategoryId"`
			} `json:"seatCategories"`
			SeatMapImage any    `json:"seatMapImage"`
			Start        int64  `json:"start"`
			VenueCode    string `json:"venueCode"`
		} `json:"performances"`
		SeatCategoryNames map[uint64]string   `json:"seatCategoryNames"`
		SubTopicNames     map[uint64]string   `json:"subTopicNames"`
		SubjectNames      map[uint64]string   `json:"subjectNames"`
		TopicNames        map[uint64]string   `json:"topicNames"`
		TopicSubTopics    map[uint64][]uint64 `json:"topicSubTopics"`
		VenueNames        map[string]string   `json:"venueNames"`
	}
)

type (
	golangRoot struct {
		Tree     *golangNode `json:"tree"`
		Username string      `json:"username"`
	}
	golangNode struct {
		Name     string       `json:"name"`
		Kids     []golangNode `json:"kids"`
		CLWeight float64      `json:"cl_weight"`
		Touches  int          `json:"touches"`
		MinT     uint64       `json:"min_t"`
		MaxT     uint64       `json:"max_t"`
		MeanT    uint64       `json:"mean_t"`
	}
)

type (
	stringRoot struct {
		Arabic                             string `json:"Arabic"`
		ArabicPresentationFormsA           string `json:"Arabic Presentation Forms-A"`
		ArabicPresentationFormsB           string `json:"Arabic Presentation Forms-B"`
		Armenian                           string `json:"Armenian"`
		Arrows                             string `json:"Arrows"`
		Bengali                            string `json:"Bengali"`
		Bopomofo                           string `json:"Bopomofo"`
		BoxDrawing                         string `json:"Box Drawing"`
		CJKCompatibility                   string `json:"CJK Compatibility"`
		CJKCompatibilityForms              string `json:"CJK Compatibility Forms"`
		CJKCompatibilityIdeographs         string `json:"CJK Compatibility Ideographs"`
		CJKSymbolsAndPunctuation           string `json:"CJK Symbols and Punctuation"`
		CJKUnifiedIdeographs               string `json:"CJK Unified Ideographs"`
		CJKUnifiedIdeographsExtensionA     string `json:"CJK Unified Ideographs Extension A"`
		CJKUnifiedIdeographsExtensionB     string `json:"CJK Unified Ideographs Extension B"`
		Cherokee                           string `json:"Cherokee"`
		CurrencySymbols                    string `json:"Currency Symbols"`
		Cyrillic                           string `json:"Cyrillic"`
		CyrillicSupplementary              string `json:"Cyrillic Supplementary"`
		Devanagari                         string `json:"Devanagari"`
		EnclosedAlphanumerics              string `json:"Enclosed Alphanumerics"`
		EnclosedCJKLettersAndMonths        string `json:"Enclosed CJK Letters and Months"`
		Ethiopic                           string `json:"Ethiopic"`
		GeometricShapes                    string `json:"Geometric Shapes"`
		Georgian                           string `json:"Georgian"`
		GreekAndCoptic                     string `json:"Greek and Coptic"`
		Gujarati                           string `json:"Gujarati"`
		Gurmukhi                           string `json:"Gurmukhi"`
		HangulCompatibilityJamo            string `json:"Hangul Compatibility Jamo"`
		HangulJamo                         string `json:"Hangul Jamo"`
		HangulSyllables                    string `json:"Hangul Syllables"`
		Hebrew                             string `json:"Hebrew"`
		Hiragana                           string `json:"Hiragana"`
		IPAExtentions                      string `json:"IPA Extentions"`
		KangxiRadicals                     string `json:"Kangxi Radicals"`
		Katakana                           string `json:"Katakana"`
		Khmer                              string `json:"Khmer"`
		KhmerSymbols                       string `json:"Khmer Symbols"`
		Latin                              string `json:"Latin"`
		LatinExtendedAdditional            string `json:"Latin Extended Additional"`
		Latin1Supplement                   string `json:"Latin-1 Supplement"`
		LatinExtendedA                     string `json:"Latin-Extended A"`
		LatinExtendedB                     string `json:"Latin-Extended B"`
		LetterlikeSymbols                  string `json:"Letterlike Symbols"`
		Malayalam                          string `json:"Malayalam"`
		MathematicalAlphanumericSymbols    string `json:"Mathematical Alphanumeric Symbols"`
		MathematicalOperators              string `json:"Mathematical Operators"`
		MiscellaneousSymbols               string `json:"Miscellaneous Symbols"`
		Mongolian                          string `json:"Mongolian"`
		NumberForms                        string `json:"Number Forms"`
		Oriya                              string `json:"Oriya"`
		PhoneticExtensions                 string `json:"Phonetic Extensions"`
		SupplementalArrowsB                string `json:"Supplemental Arrows-B"`
		Syriac                             string `json:"Syriac"`
		Tamil                              string `json:"Tamil"`
		Thaana                             string `json:"Thaana"`
		Thai                               string `json:"Thai"`
		UnifiedCanadianAboriginalSyllabics string `json:"Unified Canadian Aboriginal Syllabics"`
		YiRadicals                         string `json:"Yi Radicals"`
		YiSyllables                        string `json:"Yi Syllables"`
	}
)

type (
	syntheaRoot struct {
		Entry []struct {
			FullURL string `json:"fullUrl"`
			Request *struct {
				Method string `json:"method"`
				URL    string `json:"url"`
			} `json:"request"`
			Resource *struct {
				AbatementDateTime time.Time   `json:"abatementDateTime"`
				AchievementStatus syntheaCode `json:"achievementStatus"`
				Active            bool        `json:"active"`
				Activity          []struct {
					Detail *struct {
						Code     syntheaCode      `json:"code"`
						Location syntheaReference `json:"location"`
						Status   string           `json:"status"`
					} `json:"detail"`
				} `json:"activity"`
				Address        []syntheaAddress   `json:"address"`
				Addresses      []syntheaReference `json:"addresses"`
				AuthoredOn     time.Time          `json:"authoredOn"`
				BillablePeriod syntheaRange       `json:"billablePeriod"`
				BirthDate      string             `json:"birthDate"`
				CareTeam       []struct {
					Provider  syntheaReference `json:"provider"`
					Reference string           `json:"reference"`
					Role      syntheaCode      `json:"role"`
					Sequence  int64            `json:"sequence"`
				} `json:"careTeam"`
				Category       []syntheaCode    `json:"category"`
				Claim          syntheaReference `json:"claim"`
				Class          syntheaCoding    `json:"class"`
				ClinicalStatus syntheaCode      `json:"clinicalStatus"`
				Code           syntheaCode      `json:"code"`
				Communication  []struct {
					Language syntheaCode `json:"language"`
				} `json:"communication"`
				Component []struct {
					Code          syntheaCode   `json:"code"`
					ValueQuantity syntheaCoding `json:"valueQuantity"`
				} `json:"component"`
				Contained []struct {
					Beneficiary  syntheaReference   `json:"beneficiary"`
					ID           string             `json:"id"`
					Intent       string             `json:"intent"`
					Payor        []syntheaReference `json:"payor"`
					Performer    []syntheaReference `json:"performer"`
					Requester    syntheaReference   `json:"requester"`
					ResourceType string             `json:"resourceType"`
					Status       string             `json:"status"`
					Subject      syntheaReference   `json:"subject"`
					Type         syntheaCode        `json:"type"`
				} `json:"contained"`
				Created          time.Time   `json:"created"`
				DeceasedDateTime time.Time   `json:"deceasedDateTime"`
				Description      syntheaCode `json:"description"`
				Diagnosis        []struct {
					DiagnosisReference syntheaReference `json:"diagnosisReference"`
					Sequence           int64            `json:"sequence"`
					Type               []syntheaCode    `json:"type"`
				} `json:"diagnosis"`
				DosageInstruction []struct {
					AsNeededBoolean bool `json:"asNeededBoolean"`
					DoseAndRate     []struct {
						DoseQuantity *struct {
							Value float64 `json:"value"`
						} `json:"doseQuantity"`
						Type syntheaCode `json:"type"`
					} `json:"doseAndRate"`
					Sequence int64 `json:"sequence"`
					Timing   *struct {
						Repeat *struct {
							Frequency  int64   `json:"frequency"`
							Period     float64 `json:"period"`
							PeriodUnit string  `json:"periodUnit"`
						} `json:"repeat"`
					} `json:"timing"`
				} `json:"dosageInstruction"`
				EffectiveDateTime time.Time          `json:"effectiveDateTime"`
				Encounter         syntheaReference   `json:"encounter"`
				Extension         []syntheaExtension `json:"extension"`
				Gender            string             `json:"gender"`
				Goal              []syntheaReference `json:"goal"`
				ID                string             `json:"id"`
				Identifier        []struct {
					System string      `json:"system"`
					Type   syntheaCode `json:"type"`
					Use    string      `json:"use"`
					Value  string      `json:"value"`
				} `json:"identifier"`
				Insurance []struct {
					Coverage syntheaReference `json:"coverage"`
					Focal    bool             `json:"focal"`
					Sequence int64            `json:"sequence"`
				} `json:"insurance"`
				Insurer syntheaReference `json:"insurer"`
				Intent  string           `json:"intent"`
				Issued  time.Time        `json:"issued"`
				Item    []struct {
					Adjudication []struct {
						Amount   syntheaCurrency `json:"amount"`
						Category syntheaCode     `json:"category"`
					} `json:"adjudication"`
					Category                syntheaCode        `json:"category"`
					DiagnosisSequence       []int64            `json:"diagnosisSequence"`
					Encounter               []syntheaReference `json:"encounter"`
					InformationSequence     []int64            `json:"informationSequence"`
					LocationCodeableConcept syntheaCode        `json:"locationCodeableConcept"`
					Net                     syntheaCurrency    `json:"net"`
					ProcedureSequence       []int64            `json:"procedureSequence"`
					ProductOrService        syntheaCode        `json:"productOrService"`
					Sequence                int64              `json:"sequence"`
					ServicedPeriod          syntheaRange       `json:"servicedPeriod"`
				} `json:"item"`
				LifecycleStatus           string             `json:"lifecycleStatus"`
				ManagingOrganization      []syntheaReference `json:"managingOrganization"`
				MaritalStatus             syntheaCode        `json:"maritalStatus"`
				MedicationCodeableConcept syntheaCode        `json:"medicationCodeableConcept"`
				MultipleBirthBoolean      bool               `json:"multipleBirthBoolean"`
				Name                      rawValue           `json:"name"`
				NumberOfInstances         int64              `json:"numberOfInstances"`
				NumberOfSeries            int64              `json:"numberOfSeries"`
				OccurrenceDateTime        time.Time          `json:"occurrenceDateTime"`
				OnsetDateTime             time.Time          `json:"onsetDateTime"`
				Outcome                   string             `json:"outcome"`
				Participant               []struct {
					Individual syntheaReference `json:"individual"`
					Member     syntheaReference `json:"member"`
					Role       []syntheaCode    `json:"role"`
				} `json:"participant"`
				Patient syntheaReference `json:"patient"`
				Payment *struct {
					Amount syntheaCurrency `json:"amount"`
				} `json:"payment"`
				PerformedPeriod syntheaRange     `json:"performedPeriod"`
				Period          syntheaRange     `json:"period"`
				Prescription    syntheaReference `json:"prescription"`
				PrimarySource   bool             `json:"primarySource"`
				Priority        syntheaCode      `json:"priority"`
				Procedure       []struct {
					ProcedureReference syntheaReference `json:"procedureReference"`
					Sequence           int64            `json:"sequence"`
				} `json:"procedure"`
				Provider        syntheaReference   `json:"provider"`
				ReasonCode      []syntheaCode      `json:"reasonCode"`
				ReasonReference []syntheaReference `json:"reasonReference"`
				RecordedDate    time.Time          `json:"recordedDate"`
				Referral        syntheaReference   `json:"referral"`
				Requester       syntheaReference   `json:"requester"`
				ResourceType    string             `json:"resourceType"`
				Result          []syntheaReference `json:"result"`
				Series          []struct {
					BodySite syntheaCoding `json:"bodySite"`
					Instance []struct {
						Number   int64         `json:"number"`
						SopClass syntheaCoding `json:"sopClass"`
						Title    string        `json:"title"`
						UID      string        `json:"uid"`
					} `json:"instance"`
					Modality          syntheaCoding `json:"modality"`
					Number            int64         `json:"number"`
					NumberOfInstances int64         `json:"numberOfInstances"`
					Started           string        `json:"started"`
					UID               string        `json:"uid"`
				} `json:"series"`
				ServiceProvider syntheaReference `json:"serviceProvider"`
				Started         time.Time        `json:"started"`
				Status          string           `json:"status"`
				Subject         syntheaReference `json:"subject"`
				SupportingInfo  []struct {
					Category       syntheaCode      `json:"category"`
					Sequence       int64            `json:"sequence"`
					ValueReference syntheaReference `json:"valueReference"`
				} `json:"supportingInfo"`
				Telecom              []map[string]string `json:"telecom"`
				Text                 map[string]string   `json:"text"`
				Total                rawValue            `json:"total"`
				Type                 rawValue            `json:"type"`
				Use                  string              `json:"use"`
				VaccineCode          syntheaCode         `json:"vaccineCode"`
				ValueCodeableConcept syntheaCode         `json:"valueCodeableConcept"`
				ValueQuantity        syntheaCoding       `json:"valueQuantity"`
				VerificationStatus   syntheaCode         `json:"verificationStatus"`
			} `json:"resource"`
		} `json:"entry"`
		ResourceType string `json:"resourceType"`
		Type         string `json:"type"`
	}
	syntheaCode struct {
		Coding []syntheaCoding `json:"coding"`
		Text   string          `json:"text"`
	}
	syntheaCoding struct {
		Code    string  `json:"code"`
		Display string  `json:"display"`
		System  string  `json:"system"`
		Unit    string  `json:"unit"`
		Value   float64 `json:"value"`
	}
	syntheaReference struct {
		Display   string `json:"display"`
		Reference string `json:"reference"`
	}
	syntheaAddress struct {
		City       string             `json:"city"`
		Country    string             `json:"country"`
		Extension  []syntheaExtension `json:"extension"`
		Line       []string           `json:"line"`
		PostalCode string             `json:"postalCode"`
		State      string             `json:"state"`
	}
	syntheaExtension struct {
		URL          string             `json:"url"`
		ValueAddress syntheaAddress     `json:"valueAddress"`
		ValueCode    string             `json:"valueCode"`
		ValueDecimal float64            `json:"valueDecimal"`
		ValueString  string             `json:"valueString"`
		Extension    []syntheaExtension `json:"extension"`
	}
	syntheaRange struct {
		End   time.Time `json:"end"`
		Start time.Time `json:"start"`
	}
	syntheaCurrency struct {
		Currency string  `json:"currency"`
		Value    float64 `json:"value"`
	}
)

type (
	twitterRoot struct {
		Statuses       []twitterStatus `json:"statuses"`
		SearchMetadata struct {
			CompletedIn float64 `json:"completed_in"`
			MaxID       int64   `json:"max_id"`
			MaxIDStr    int64   `json:"max_id_str,string"`
			NextResults string  `json:"next_results"`
			Query       string  `json:"query"`
			RefreshURL  string  `json:"refresh_url"`
			Count       int     `json:"count"`
			SinceID     int     `json:"since_id"`
			SinceIDStr  int     `json:"since_id_str,string"`
		} `json:"search_metadata"`
	}
	twitterStatus struct {
		Metadata struct {
			ResultType      string `json:"result_type"`
			IsoLanguageCode string `json:"iso_language_code"`
		} `json:"metadata"`
		CreatedAt            string          `json:"created_at"`
		ID                   int64           `json:"id"`
		IDStr                int64           `json:"id_str,string"`
		Text                 string          `json:"text"`
		Source               string          `json:"source"`
		Truncated            bool            `json:"truncated"`
		InReplyToStatusID    int64           `json:"in_reply_to_status_id"`
		InReplyToStatusIDStr int64           `json:"in_reply_to_status_id_str,string"`
		InReplyToUserID      int64           `json:"in_reply_to_user_id"`
		InReplyToUserIDStr   int64           `json:"in_reply_to_user_id_str,string"`
		InReplyToScreenName  string          `json:"in_reply_to_screen_name"`
		User                 twitterUser     `json:"user,omitempty"`
		Geo                  any             `json:"geo"`
		Coordinates          any             `json:"coordinates"`
		Place                any             `json:"place"`
		Contributors         any             `json:"contributors"`
		RetweeetedStatus     *twitterStatus  `json:"retweeted_status"`
		RetweetCount         int             `json:"retweet_count"`
		FavoriteCount        int             `json:"favorite_count"`
		Entities             twitterEntities `json:"entities,omitempty"`
		Favorited            bool            `json:"favorited"`
		Retweeted            bool            `json:"retweeted"`
		PossiblySensitive    bool            `json:"possibly_sensitive"`
		Lang                 string          `json:"lang"`
	}
	twitterUser struct {
		ID                             int64           `json:"id"`
		IDStr                          string          `json:"id_str"`
		Name                           string          `json:"name"`
		ScreenName                     string          `json:"screen_name"`
		Location                       string          `json:"location"`
		Description                    string          `json:"description"`
		URL                            any             `json:"url"`
		Entities                       twitterEntities `json:"entities"`
		Protected                      bool            `json:"protected"`
		FollowersCount                 int             `json:"followers_count"`
		FriendsCount                   int             `json:"friends_count"`
		ListedCount                    int             `json:"listed_count"`
		CreatedAt                      string          `json:"created_at"`
		FavouritesCount                int             `json:"favourites_count"`
		UtcOffset                      int             `json:"utc_offset"`
		TimeZone                       string          `json:"time_zone"`
		GeoEnabled                     bool            `json:"geo_enabled"`
		Verified                       bool            `json:"verified"`
		StatusesCount                  int             `json:"statuses_count"`
		Lang                           string          `json:"lang"`
		ContributorsEnabled            bool            `json:"contributors_enabled"`
		IsTranslator                   bool            `json:"is_translator"`
		IsTranslationEnabled           bool            `json:"is_translation_enabled"`
		ProfileBackgroundColor         string          `json:"profile_background_color"`
		ProfileBackgroundImageURL      string          `json:"profile_background_image_url"`
		ProfileBackgroundImageURLHTTPS string          `json:"profile_background_image_url_https"`
		ProfileBackgroundTile          bool            `json:"profile_background_tile"`
		ProfileImageURL                string          `json:"profile_image_url"`
		ProfileImageURLHTTPS           string          `json:"profile_image_url_https"`
		ProfileBannerURL               string          `json:"profile_banner_url"`
		ProfileLinkColor               string          `json:"profile_link_color"`
		ProfileSidebarBorderColor      string          `json:"profile_sidebar_border_color"`
		ProfileSidebarFillColor        string          `json:"profile_sidebar_fill_color"`
		ProfileTextColor               string          `json:"profile_text_color"`
		ProfileUseBackgroundImage      bool            `json:"profile_use_background_image"`
		DefaultProfile                 bool            `json:"default_profile"`
		DefaultProfileImage            bool            `json:"default_profile_image"`
		Following                      bool            `json:"following"`
		FollowRequestSent              bool            `json:"follow_request_sent"`
		Notifications                  bool            `json:"notifications"`
	}
	twitterEntities struct {
		Hashtags     []any        `json:"hashtags"`
		Symbols      []any        `json:"symbols"`
		URL          *twitterURL  `json:"url"`
		URLs         []twitterURL `json:"urls"`
		UserMentions []struct {
			ScreenName string `json:"screen_name"`
			Name       string `json:"name"`
			ID         int64  `json:"id"`
			IDStr      int64  `json:"id_str,string"`
			Indices    []int  `json:"indices"`
		} `json:"user_mentions"`
		Description struct {
			URLs []twitterURL `json:"urls"`
		} `json:"description"`
		Media []struct {
			ID            int64  `json:"id"`
			IDStr         string `json:"id_str"`
			Indices       []int  `json:"indices"`
			MediaURL      string `json:"media_url"`
			MediaURLHTTPS string `json:"media_url_https"`
			URL           string `json:"url"`
			DisplayURL    string `json:"display_url"`
			ExpandedURL   string `json:"expanded_url"`
			Type          string `json:"type"`
			Sizes         map[string]struct {
				W      int    `json:"w"`
				H      int    `json:"h"`
				Resize string `json:"resize"`
			} `json:"sizes"`
			SourceStatusID    int64 `json:"source_status_id"`
			SourceStatusIDStr int64 `json:"source_status_id_str,string"`
		} `json:"media"`
	}
	twitterURL struct {
		URL         string       `json:"url"`
		URLs        []twitterURL `json:"urls"`
		ExpandedURL string       `json:"expanded_url"`
		DisplayURL  string       `json:"display_url"`
		Indices     []int        `json:"indices"`
	}
)

// rawValue is the raw encoded JSON value.
type rawValue []byte

func (v rawValue) MarshalJSON() ([]byte, error) {
	if v == nil {
		return []byte("null"), nil
	}
	return v, nil
}

func (v *rawValue) UnmarshalJSON(b []byte) error {
	if v == nil {
		return errors.New("jsontest.rawValue: UnmarshalJSON on nil pointer")
	}
	*v = append((*v)[:0], b...)
	return nil
}
