How to use SubJSONOf method of td Package

Best Go-testdeep code snippet using td.SubJSONOf

td_json.go

Source:td_json.go Github

copy

Full Screen

...21 "github.com/maxatome/go-testdeep/internal/types"22 "github.com/maxatome/go-testdeep/internal/util"23)24// forbiddenOpsInJSON contains operators forbidden inside JSON,25// SubJSONOf or SuperJSONOf, optionally with an alternative to help26// the user.27var forbiddenOpsInJSON = map[string]string{28 "Array": "literal []",29 "Cap": "",30 "Catch": "",31 "Code": "",32 "Delay": "",33 "ErrorIs": "",34 "Isa": "",35 "JSON": "literal JSON",36 "Lax": "",37 "Map": "literal {}",38 "PPtr": "",39 "Ptr": "",40 "Recv": "",41 "SStruct": "",42 "Shallow": "",43 "Slice": "literal []",44 "Smuggle": "",45 "String": `literal ""`,46 "SubJSONOf": "SubMapOf operator",47 "SuperJSONOf": "SuperMapOf operator",48 "SuperSliceOf": "All and JSONPointer operators",49 "Struct": "",50 "Tag": "",51 "TruncTime": "",52}53// tdJSONUnmarshaler handles the JSON unmarshaling of JSON, SubJSONOf54// and SuperJSONOf first parameter.55type tdJSONUnmarshaler struct {56 location.Location // position of the operator57}58// newJSONUnmarshaler returns a new instance of tdJSONUnmarshaler.59func newJSONUnmarshaler(pos location.Location) tdJSONUnmarshaler {60 return tdJSONUnmarshaler{61 Location: pos,62 }63}64// replaceLocation replaces the location of tdOp by the65// JSON/SubJSONOf/SuperJSONOf one then add the position of the66// operator inside the JSON string.67func (u tdJSONUnmarshaler) replaceLocation(tdOp TestDeep, posInJSON json.Position) {68 // The goal, instead of:69 // [under operator Len at value.go:476]70 // having:71 // [under operator Len at line 12:7 (pos 123) inside operator JSON at file.go:23]72 // so add ^------------------------------------------^73 newPos := u.Location74 newPos.Inside = fmt.Sprintf("%s inside operator %s ", posInJSON, u.Func)75 newPos.Func = tdOp.GetLocation().Func76 tdOp.replaceLocation(newPos)77}78// unmarshal unmarshals expectedJSON using placeholder parameters params.79func (u tdJSONUnmarshaler) unmarshal(expectedJSON any, params []any) (any, *ctxerr.Error) {80 var (81 err error82 b []byte83 )84 switch data := expectedJSON.(type) {85 case string:86 // Try to load this file (if it seems it can be a filename and not87 // a JSON content)88 if strings.HasSuffix(data, ".json") {89 // It could be a file name, try to read from it90 b, err = os.ReadFile(data)91 if err != nil {92 return nil, ctxerr.OpBad(u.Func, "JSON file %s cannot be read: %s", data, err)93 }94 break95 }96 b = []byte(data)97 case []byte:98 b = data99 case io.Reader:100 b, err = io.ReadAll(data)101 if err != nil {102 return nil, ctxerr.OpBad(u.Func, "JSON read error: %s", err)103 }104 default:105 return nil, ctxerr.OpBadUsage(106 u.Func, "(STRING_JSON|STRING_FILENAME|[]byte|io.Reader, ...)",107 expectedJSON, 1, false)108 }109 params = flat.Interfaces(params...)110 var byTag map[string]any111 for i, p := range params {112 switch op := p.(type) {113 case *tdTag:114 if byTag[op.tag] != nil {115 return nil, ctxerr.OpBad(u.Func, `2 params have the same tag "%s"`, op.tag)116 }117 if byTag == nil {118 byTag = map[string]any{}119 }120 // Don't keep the tag layer121 p = nil122 if op.expectedValue.IsValid() {123 p = op.expectedValue.Interface()124 }125 byTag[op.tag] = newJSONNamedPlaceholder(op.tag, p)126 default:127 params[i] = newJSONNumPlaceholder(uint64(i+1), p)128 }129 }130 final, err := json.Parse(b, json.ParseOpts{131 Placeholders: params,132 PlaceholdersByName: byTag,133 OpFn: u.resolveOp(),134 })135 if err != nil {136 return nil, ctxerr.OpBad(u.Func, "JSON unmarshal error: %s", err)137 }138 return final, nil139}140// resolveOp returns a closure usable as json.ParseOpts.OpFn.141func (u tdJSONUnmarshaler) resolveOp() func(json.Operator, json.Position) (any, error) {142 return func(jop json.Operator, posInJSON json.Position) (any, error) {143 op, exists := allOperators[jop.Name]144 if !exists {145 return nil, fmt.Errorf("unknown operator %s()", jop.Name)146 }147 if hint, exists := forbiddenOpsInJSON[jop.Name]; exists {148 if hint == "" {149 return nil, fmt.Errorf("%s() is not usable in JSON()", jop.Name)150 }151 return nil, fmt.Errorf("%s() is not usable in JSON(), use %s instead",152 jop.Name, hint)153 }154 vfn := reflect.ValueOf(op)155 tfn := vfn.Type()156 // If some parameters contain a placeholder, dereference it157 for i, p := range jop.Params {158 if ph, ok := p.(*tdJSONPlaceholder); ok {159 jop.Params[i] = ph.expectedValue.Interface()160 }161 }162 // Special cases163 var min, max int164 addNilParam := false165 switch jop.Name {166 case "Between":167 min, max = 2, 3168 if len(jop.Params) == 3 {169 bad := false170 switch tp := jop.Params[2].(type) {171 case BoundsKind:172 // Special case, accept numeric values of Bounds*173 // constants, for the case:174 // td.JSON(`Between(40, 42, $1)`, td.BoundsInOut)175 case string:176 switch tp {177 case "[]", "BoundsInIn":178 jop.Params[2] = BoundsInIn179 case "[[", "BoundsInOut":180 jop.Params[2] = BoundsInOut181 case "]]", "BoundsOutIn":182 jop.Params[2] = BoundsOutIn183 case "][", "BoundsOutOut":184 jop.Params[2] = BoundsOutOut185 default:186 bad = true187 }188 default:189 bad = true190 }191 if bad {192 return nil, errors.New(`Between() bad 3rd parameter, use "[]", "[[", "]]" or "]["`)193 }194 }195 case "N", "Re":196 min, max = 1, 2197 case "SubMapOf", "SuperMapOf":198 min, max, addNilParam = 1, 1, true199 default:200 min = tfn.NumIn()201 if tfn.IsVariadic() {202 // for All(expected ...any) → min == 1, as All() is a non-sense203 max = -1204 } else {205 max = min206 }207 }208 if len(jop.Params) < min || (max >= 0 && len(jop.Params) > max) {209 switch {210 case max < 0:211 return nil, fmt.Errorf("%s() requires at least one parameter", jop.Name)212 case max == 0:213 return nil, fmt.Errorf("%s() requires no parameters", jop.Name)214 case min == max:215 if min == 1 {216 return nil, fmt.Errorf("%s() requires only one parameter", jop.Name)217 }218 return nil, fmt.Errorf("%s() requires %d parameters", jop.Name, min)219 default:220 return nil, fmt.Errorf("%s() requires %d or %d parameters", jop.Name, min, max)221 }222 }223 var in []reflect.Value224 if len(jop.Params) > 0 {225 in = make([]reflect.Value, len(jop.Params))226 for i, p := range jop.Params {227 in[i] = reflect.ValueOf(p)228 }229 if addNilParam {230 in = append(in, reflect.ValueOf(MapEntries(nil)))231 }232 // If the function is variadic, no need to check each param as all233 // variadic operators have always a ...any234 numCheck := len(in)235 if tfn.IsVariadic() {236 numCheck = tfn.NumIn() - 1237 }238 for i, p := range in[:numCheck] {239 fpt := tfn.In(i)240 if fpt.Kind() != reflect.Interface && p.Type() != fpt {241 return nil, fmt.Errorf(242 "%s() bad #%d parameter type: %s required but %s received",243 jop.Name, i+1,244 fpt, p.Type(),245 )246 }247 }248 }249 tdOp := vfn.Call(in)[0].Interface().(TestDeep)250 // let erroneous operators (tdOp.err != nil) pass251 // replace the location by the JSON/SubJSONOf/SuperJSONOf one252 u.replaceLocation(tdOp, posInJSON)253 return newJSONEmbedded(tdOp), nil254 }255}256// tdJSONSmuggler is the base type for tdJSONPlaceholder & tdJSONEmbedded.257type tdJSONSmuggler struct {258 tdSmugglerBase // ignored by tools/gen_funcs.pl259}260func (s *tdJSONSmuggler) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {261 vgot, _ := jsonify(ctx, got) // Cannot fail262 // Here, vgot type is either a bool, float64, string,263 // []any, a map[string]any or simply nil264 return s.jsonValueEqual(ctx, vgot)265}266func (s *tdJSONSmuggler) String() string {267 return util.ToString(s.expectedValue.Interface())268}269func (s *tdJSONSmuggler) HandleInvalid() bool {270 return true271}272func (s *tdJSONSmuggler) TypeBehind() reflect.Type {273 return s.internalTypeBehind()274}275// tdJSONPlaceholder is an internal smuggler operator. It represents a276// JSON placeholder in an unmarshaled JSON expected data structure. As $1 in:277//278// td.JSON(`{"foo": $1}`, td.Between(12, 34))279//280// It takes the JSON representation of data and compares it to281// expectedValue.282//283// It does its best to convert back the JSON pointed data to the type284// of expectedValue or to the type behind the expectedValue.285type tdJSONPlaceholder struct {286 tdJSONSmuggler287 name string288 num uint64289}290func newJSONNamedPlaceholder(name string, expectedValue any) TestDeep {291 p := tdJSONPlaceholder{292 tdJSONSmuggler: tdJSONSmuggler{293 tdSmugglerBase: newSmugglerBase(expectedValue, 1),294 },295 name: name,296 }297 if !p.isTestDeeper {298 p.expectedValue = reflect.ValueOf(expectedValue)299 }300 return &p301}302func newJSONNumPlaceholder(num uint64, expectedValue any) TestDeep {303 p := tdJSONPlaceholder{304 tdJSONSmuggler: tdJSONSmuggler{305 tdSmugglerBase: newSmugglerBase(expectedValue, 1),306 },307 num: num,308 }309 if !p.isTestDeeper {310 p.expectedValue = reflect.ValueOf(expectedValue)311 }312 return &p313}314func (p *tdJSONPlaceholder) MarshalJSON() ([]byte, error) {315 if !p.isTestDeeper {316 var expected any317 if p.expectedValue.IsValid() {318 expected = p.expectedValue.Interface()319 }320 return ejson.Marshal(expected)321 }322 var b bytes.Buffer323 if p.num == 0 {324 fmt.Fprintf(&b, `"$%s"`, p.name)325 } else {326 fmt.Fprintf(&b, `"$%d"`, p.num)327 }328 b.WriteString(` /* `)329 indent := "\n" + strings.Repeat(" ", b.Len())330 b.WriteString(strings.ReplaceAll(p.String(), "\n", indent))331 b.WriteString(` */`)332 return b.Bytes(), nil333}334// tdJSONEmbedded represents a MarshalJSON'able operator. As Between() in:335//336// td.JSON(`{"foo": Between(12, 34)}`)337//338// tdSmugglerBase always contains a TestDeep operator, newJSONEmbedded()339// ensures that.340//341// It does its best to convert back the JSON pointed data to the type342// of the type behind the expectedValue (which is always a TestDeep343// operator).344type tdJSONEmbedded struct {345 tdJSONSmuggler346}347func newJSONEmbedded(tdOp TestDeep) TestDeep {348 return &tdJSONEmbedded{349 tdJSONSmuggler: tdJSONSmuggler{350 tdSmugglerBase: newSmugglerBase(tdOp, 1),351 },352 }353}354func (e *tdJSONEmbedded) MarshalJSON() ([]byte, error) {355 return []byte(e.String()), nil356}357// tdJSON is the JSON operator.358type tdJSON struct {359 baseOKNil360 expected reflect.Value361}362var _ TestDeep = &tdJSON{}363func gotViaJSON(ctx ctxerr.Context, pGot *reflect.Value) *ctxerr.Error {364 got, err := jsonify(ctx, *pGot)365 if err != nil {366 return err367 }368 *pGot = reflect.ValueOf(got)369 return nil370}371func jsonify(ctx ctxerr.Context, got reflect.Value) (any, *ctxerr.Error) {372 gotIf, ok := dark.GetInterface(got, true)373 if !ok {374 return nil, ctx.CannotCompareError()375 }376 b, err := ejson.Marshal(gotIf)377 if err != nil {378 if ctx.BooleanError {379 return nil, ctxerr.BooleanError380 }381 return nil, &ctxerr.Error{382 Message: "json.Marshal failed",383 Summary: ctxerr.NewSummary(err.Error()),384 }385 }386 // As Marshal succeeded, Unmarshal in an any cannot fail387 var vgot any388 ejson.Unmarshal(b, &vgot) //nolint: errcheck389 return vgot, nil390}391// summary(JSON): compares against JSON representation392// input(JSON): nil,bool,str,int,float,array,slice,map,struct,ptr393// JSON operator allows to compare the JSON representation of data394// against expectedJSON. expectedJSON can be a:395//396// - string containing JSON data like `{"fullname":"Bob","age":42}`397// - string containing a JSON filename, ending with ".json" (its398// content is [os.ReadFile] before unmarshaling)399// - []byte containing JSON data400// - [io.Reader] stream containing JSON data (is [io.ReadAll]401// before unmarshaling)402//403// expectedJSON JSON value can contain placeholders. The params404// are for any placeholder parameters in expectedJSON. params can405// contain [TestDeep] operators as well as raw values. A placeholder can406// be numeric like $2 or named like $name and always references an407// item in params.408//409// Numeric placeholders reference the n'th "operators" item (starting410// at 1). Named placeholders are used with [Tag] operator as follows:411//412// td.Cmp(t, gotValue,413// td.JSON(`{"fullname": $name, "age": $2, "gender": $3}`,414// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name415// td.Between(41, 43), // matches only $2416// "male")) // matches only $3417//418// Note that placeholders can be double-quoted as in:419//420// td.Cmp(t, gotValue,421// td.JSON(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,422// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name423// td.Between(41, 43), // matches only $2424// "male")) // matches only $3425//426// It makes no difference whatever the underlying type of the replaced427// item is (= double quoting a placeholder matching a number is not a428// problem). It is just a matter of taste, double-quoting placeholders429// can be preferred when the JSON data has to conform to the JSON430// specification, like when used in a ".json" file.431//432// JSON does its best to convert back the JSON corresponding to a433// placeholder to the type of the placeholder or, if the placeholder434// is an operator, to the type behind the operator. Allowing to do435// things like:436//437// td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, []int{1, 2, 3, 4}))438// td.Cmp(t, gotValue,439// td.JSON(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))440// td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, td.Between(27, 32)))441//442// Of course, it does this conversion only if the expected type can be443// guessed. In the case the conversion cannot occur, data is compared444// as is, in its freshly unmarshaled JSON form (so as bool, float64,445// string, []any, map[string]any or simply nil).446//447// Note expectedJSON can be a []byte, a JSON filename or a [io.Reader]:448//449// td.Cmp(t, gotValue, td.JSON("file.json", td.Between(12, 34)))450// td.Cmp(t, gotValue, td.JSON([]byte(`[1, $1, 3]`), td.Between(12, 34)))451// td.Cmp(t, gotValue, td.JSON(osFile, td.Between(12, 34)))452//453// A JSON filename ends with ".json".454//455// To avoid a legit "$" string prefix causes a bad placeholder error,456// just double it to escape it. Note it is only needed when the "$" is457// the first character of a string:458//459// td.Cmp(t, gotValue,460// td.JSON(`{"fullname": "$name", "details": "$$info", "age": $2}`,461// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name462// td.Between(41, 43))) // matches only $2463//464// For the "details" key, the raw value "$info" is expected, no465// placeholders are involved here.466//467// Note that [Lax] mode is automatically enabled by JSON operator to468// simplify numeric tests.469//470// Comments can be embedded in JSON data:471//472// td.Cmp(t, gotValue,473// td.JSON(`474// {475// // A guy properties:476// "fullname": "$name", // The full name of the guy477// "details": "$$info", // Literally "$info", thanks to "$" escape478// "age": $2 /* The age of the guy:479// - placeholder unquoted, but could be without480// any change481// - to demonstrate a multi-lines comment */482// }`,483// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name484// td.Between(41, 43))) // matches only $2485//486// Comments, like in go, have 2 forms. To quote the Go language specification:487// - line comments start with the character sequence // and stop at the488// end of the line.489// - multi-lines comments start with the character sequence /* and stop490// with the first subsequent character sequence */.491//492// Other JSON divergences:493// - ',' can precede a '}' or a ']' (as in go);494// - strings can contain non-escaped \n, \r and \t;495// - raw strings are accepted (r{raw}, r!raw!, …), see below;496// - int_lit & float_lit numbers as defined in go spec are accepted;497// - numbers can be prefixed by '+'.498//499// Most operators can be directly embedded in JSON without requiring500// any placeholder. If an operators does not take any parameter, the501// parenthesis can be omitted.502//503// td.Cmp(t, gotValue,504// td.JSON(`505// {506// "fullname": HasPrefix("Foo"),507// "age": Between(41, 43),508// "details": SuperMapOf({509// "address": NotEmpty, // () are optional when no parameters510// "car": Any("Peugeot", "Tesla", "Jeep") // any of these511// })512// }`))513//514// Placeholders can be used anywhere, even in operators parameters as in:515//516// td.Cmp(t, gotValue, td.JSON(`{"fullname": HasPrefix($1)}`, "Zip"))517//518// A few notes about operators embedding:519// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;520// - the optional 3rd parameter of [Between] has to be specified as a string521// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",522// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";523// - not all operators are embeddable only the following are: [All],524// [Any], [ArrayEach], [Bag], [Between], [Contains],525// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],526// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],527// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],528// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],529// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],530// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]531// and [Zero].532//533// It is also possible to embed operators in JSON strings. This way,534// the JSON specification can be fulfilled. To avoid collision with535// possible strings, just prefix the first operator name with536// "$^". The previous example becomes:537//538// td.Cmp(t, gotValue,539// td.JSON(`540// {541// "fullname": "$^HasPrefix(\"Foo\")",542// "age": "$^Between(41, 43)",543// "details": "$^SuperMapOf({544// \"address\": NotEmpty, // () are optional when no parameters545// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these546// })"547// }`))548//549// As you can see, in this case, strings in strings have to be550// escaped. Fortunately, newlines are accepted, but unfortunately they551// are forbidden by JSON specification. To avoid too much escaping,552// raw strings are accepted. A raw string is a "r" followed by a553// delimiter, the corresponding delimiter closes the string. The554// following raw strings are all the same as "foo\\bar(\"zip\")!":555// - r'foo\bar"zip"!'556// - r,foo\bar"zip"!,557// - r%foo\bar"zip"!%558// - r(foo\bar("zip")!)559// - r{foo\bar("zip")!}560// - r[foo\bar("zip")!]561// - r<foo\bar("zip")!>562//563// So non-bracketing delimiters use the same character before and564// after, but the 4 sorts of ASCII brackets (round, angle, square,565// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot566// be escaped.567//568// With raw strings, the previous example becomes:569//570// td.Cmp(t, gotValue,571// td.JSON(`572// {573// "fullname": "$^HasPrefix(r<Foo>)",574// "age": "$^Between(41, 43)",575// "details": "$^SuperMapOf({576// r<address>: NotEmpty, // () are optional when no parameters577// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these578// })"579// }`))580//581// Note that raw strings are accepted anywhere, not only in original582// JSON strings.583//584// To be complete, $^ can prefix an operator even outside a585// string. This is accepted for compatibility purpose as the first586// operator embedding feature used this way to embed some operators.587//588// So the following calls are all equivalent:589//590// td.Cmp(t, gotValue, td.JSON(`{"id": $1}`, td.NotZero()))591// td.Cmp(t, gotValue, td.JSON(`{"id": NotZero}`))592// td.Cmp(t, gotValue, td.JSON(`{"id": NotZero()}`))593// td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero}`))594// td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero()}`))595// td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero"}`))596// td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero()"}`))597//598// As for placeholders, there is no differences between $^NotZero and599// "$^NotZero".600//601// TypeBehind method returns the [reflect.Type] of the expectedJSON602// once JSON unmarshaled. So it can be bool, string, float64, []any,603// map[string]any or any in case expectedJSON is "null".604//605// See also [JSONPointer], [SubJSONOf] and [SuperJSONOf].606func JSON(expectedJSON any, params ...any) TestDeep {607 j := &tdJSON{608 baseOKNil: newBaseOKNil(3),609 }610 v, err := newJSONUnmarshaler(j.GetLocation()).unmarshal(expectedJSON, params)611 if err != nil {612 j.err = err613 } else {614 j.expected = reflect.ValueOf(v)615 }616 return j617}618func (j *tdJSON) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {619 if j.err != nil {620 return ctx.CollectError(j.err)621 }622 err := gotViaJSON(ctx, &got)623 if err != nil {624 return ctx.CollectError(err)625 }626 ctx.BeLax = true627 return deepValueEqual(ctx, got, j.expected)628}629func (j *tdJSON) String() string {630 if j.err != nil {631 return j.stringError()632 }633 return jsonStringify("JSON", j.expected)634}635func jsonStringify(opName string, v reflect.Value) string {636 if !v.IsValid() {637 return "JSON(null)"638 }639 var b bytes.Buffer640 b.WriteString(opName)641 b.WriteByte('(')642 json.AppendMarshal(&b, v.Interface(), len(opName)+1) //nolint: errcheck643 b.WriteByte(')')644 return b.String()645}646func (j *tdJSON) TypeBehind() reflect.Type {647 if j.err != nil {648 return nil649 }650 if j.expected.IsValid() {651 // In case we have an operator at the root, delegate it the call652 if tdOp, ok := j.expected.Interface().(TestDeep); ok {653 return tdOp.TypeBehind()654 }655 return j.expected.Type()656 }657 return types.Interface658}659type tdMapJSON struct {660 tdMap661 expected reflect.Value662}663var _ TestDeep = &tdMapJSON{}664// summary(SubJSONOf): compares struct or map against JSON665// representation but with potentially some exclusions666// input(SubJSONOf): map,struct,ptr(ptr on map/struct)667// SubJSONOf operator allows to compare the JSON representation of668// data against expectedJSON. Unlike [JSON] operator, marshaled data669// must be a JSON object/map (aka {…}). expectedJSON can be a:670//671// - string containing JSON data like `{"fullname":"Bob","age":42}`672// - string containing a JSON filename, ending with ".json" (its673// content is [os.ReadFile] before unmarshaling)674// - []byte containing JSON data675// - [io.Reader] stream containing JSON data (is [io.ReadAll] before676// unmarshaling)677//678// JSON data contained in expectedJSON must be a JSON object/map679// (aka {…}) too. During a match, each expected entry should match in680// the compared map. But some expected entries can be missing from the681// compared map.682//683// type MyStruct struct {684// Name string `json:"name"`685// Age int `json:"age"`686// }687// got := MyStruct{688// Name: "Bob",689// Age: 42,690// }691// td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "age": 42, "city": "NY"}`)) // succeeds692// td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, extra "age"693//694// expectedJSON JSON value can contain placeholders. The params695// are for any placeholder parameters in expectedJSON. params can696// contain [TestDeep] operators as well as raw values. A placeholder can697// be numeric like $2 or named like $name and always references an698// item in params.699//700// Numeric placeholders reference the n'th "operators" item (starting701// at 1). Named placeholders are used with [Tag] operator as follows:702//703// td.Cmp(t, gotValue,704// td.SubJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,705// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name706// td.Between(41, 43), // matches only $2707// "male")) // matches only $3708//709// Note that placeholders can be double-quoted as in:710//711// td.Cmp(t, gotValue,712// td.SubJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,713// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name714// td.Between(41, 43), // matches only $2715// "male")) // matches only $3716//717// It makes no difference whatever the underlying type of the replaced718// item is (= double quoting a placeholder matching a number is not a719// problem). It is just a matter of taste, double-quoting placeholders720// can be preferred when the JSON data has to conform to the JSON721// specification, like when used in a ".json" file.722//723// SubJSONOf does its best to convert back the JSON corresponding to a724// placeholder to the type of the placeholder or, if the placeholder725// is an operator, to the type behind the operator. Allowing to do726// things like:727//728// td.Cmp(t, gotValue,729// td.SubJSONOf(`{"foo":$1, "bar": 12}`, []int{1, 2, 3, 4}))730// td.Cmp(t, gotValue,731// td.SubJSONOf(`{"foo":$1, "bar": 12}`, []any{1, 2, td.Between(2, 4), 4}))732// td.Cmp(t, gotValue,733// td.SubJSONOf(`{"foo":$1, "bar": 12}`, td.Between(27, 32)))734//735// Of course, it does this conversion only if the expected type can be736// guessed. In the case the conversion cannot occur, data is compared737// as is, in its freshly unmarshaled JSON form (so as bool, float64,738// string, []any, map[string]any or simply nil).739//740// Note expectedJSON can be a []byte, JSON filename or [io.Reader]:741//742// td.Cmp(t, gotValue, td.SubJSONOf("file.json", td.Between(12, 34)))743// td.Cmp(t, gotValue, td.SubJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))744// td.Cmp(t, gotValue, td.SubJSONOf(osFile, td.Between(12, 34)))745//746// A JSON filename ends with ".json".747//748// To avoid a legit "$" string prefix causes a bad placeholder error,749// just double it to escape it. Note it is only needed when the "$" is750// the first character of a string:751//752// td.Cmp(t, gotValue,753// td.SubJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,754// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name755// td.Between(41, 43))) // matches only $2756//757// For the "details" key, the raw value "$info" is expected, no758// placeholders are involved here.759//760// Note that [Lax] mode is automatically enabled by SubJSONOf operator to761// simplify numeric tests.762//763// Comments can be embedded in JSON data:764//765// td.Cmp(t, gotValue,766// SubJSONOf(`767// {768// // A guy properties:769// "fullname": "$name", // The full name of the guy770// "details": "$$info", // Literally "$info", thanks to "$" escape771// "age": $2 /* The age of the guy:772// - placeholder unquoted, but could be without773// any change774// - to demonstrate a multi-lines comment */775// }`,776// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name777// td.Between(41, 43))) // matches only $2778//779// Comments, like in go, have 2 forms. To quote the Go language specification:780// - line comments start with the character sequence // and stop at the781// end of the line.782// - multi-lines comments start with the character sequence /* and stop783// with the first subsequent character sequence */.784//785// Other JSON divergences:786// - ',' can precede a '}' or a ']' (as in go);787// - strings can contain non-escaped \n, \r and \t;788// - raw strings are accepted (r{raw}, r!raw!, …), see below;789// - int_lit & float_lit numbers as defined in go spec are accepted;790// - numbers can be prefixed by '+'.791//792// Most operators can be directly embedded in SubJSONOf without requiring793// any placeholder. If an operators does not take any parameter, the794// parenthesis can be omitted.795//796// td.Cmp(t, gotValue,797// td.SubJSONOf(`798// {799// "fullname": HasPrefix("Foo"),800// "age": Between(41, 43),801// "details": SuperMapOf({802// "address": NotEmpty, // () are optional when no parameters803// "car": Any("Peugeot", "Tesla", "Jeep") // any of these804// })805// }`))806//807// Placeholders can be used anywhere, even in operators parameters as in:808//809// td.Cmp(t, gotValue,810// td.SubJSONOf(`{"fullname": HasPrefix($1), "bar": 42}`, "Zip"))811//812// A few notes about operators embedding:813// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;814// - the optional 3rd parameter of [Between] has to be specified as a string815// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",816// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";817// - not all operators are embeddable only the following are: [All],818// [Any], [ArrayEach], [Bag], [Between], [Contains],819// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],820// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],821// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],822// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],823// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],824// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]825// and [Zero].826//827// It is also possible to embed operators in JSON strings. This way,828// the JSON specification can be fulfilled. To avoid collision with829// possible strings, just prefix the first operator name with830// "$^". The previous example becomes:831//832// td.Cmp(t, gotValue,833// td.SubJSONOf(`834// {835// "fullname": "$^HasPrefix(\"Foo\")",836// "age": "$^Between(41, 43)",837// "details": "$^SuperMapOf({838// \"address\": NotEmpty, // () are optional when no parameters839// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these840// })"841// }`))842//843// As you can see, in this case, strings in strings have to be844// escaped. Fortunately, newlines are accepted, but unfortunately they845// are forbidden by JSON specification. To avoid too much escaping,846// raw strings are accepted. A raw string is a "r" followed by a847// delimiter, the corresponding delimiter closes the string. The848// following raw strings are all the same as "foo\\bar(\"zip\")!":849// - r'foo\bar"zip"!'850// - r,foo\bar"zip"!,851// - r%foo\bar"zip"!%852// - r(foo\bar("zip")!)853// - r{foo\bar("zip")!}854// - r[foo\bar("zip")!]855// - r<foo\bar("zip")!>856//857// So non-bracketing delimiters use the same character before and858// after, but the 4 sorts of ASCII brackets (round, angle, square,859// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot860// be escaped.861//862// With raw strings, the previous example becomes:863//864// td.Cmp(t, gotValue,865// td.SubJSONOf(`866// {867// "fullname": "$^HasPrefix(r<Foo>)",868// "age": "$^Between(41, 43)",869// "details": "$^SuperMapOf({870// r<address>: NotEmpty, // () are optional when no parameters871// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these872// })"873// }`))874//875// Note that raw strings are accepted anywhere, not only in original876// JSON strings.877//878// To be complete, $^ can prefix an operator even outside a879// string. This is accepted for compatibility purpose as the first880// operator embedding feature used this way to embed some operators.881//882// So the following calls are all equivalent:883//884// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $1}`, td.NotZero()))885// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero}`))886// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero()}`))887// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero}`))888// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero()}`))889// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero"}`))890// td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero()"}`))891//892// As for placeholders, there is no differences between $^NotZero and893// "$^NotZero".894//895// TypeBehind method returns the map[string]any type.896//897// See also [JSON], [JSONPointer] and [SuperJSONOf].898func SubJSONOf(expectedJSON any, params ...any) TestDeep {899 m := &tdMapJSON{900 tdMap: tdMap{901 tdExpectedType: tdExpectedType{902 base: newBase(3),903 expectedType: reflect.TypeOf((map[string]any)(nil)),904 },905 kind: subMap,906 },907 }908 v, err := newJSONUnmarshaler(m.GetLocation()).unmarshal(expectedJSON, params)909 if err != nil {910 m.err = err911 return m912 }913 _, ok := v.(map[string]any)914 if !ok {915 m.err = ctxerr.OpBad("SubJSONOf", "SubJSONOf() only accepts JSON objects {…}")916 return m917 }918 m.expected = reflect.ValueOf(v)919 m.populateExpectedEntries(nil, m.expected)920 return m921}922// summary(SuperJSONOf): compares struct or map against JSON923// representation but with potentially extra entries924// input(SuperJSONOf): map,struct,ptr(ptr on map/struct)925// SuperJSONOf operator allows to compare the JSON representation of926// data against expectedJSON. Unlike JSON operator, marshaled data927// must be a JSON object/map (aka {…}). expectedJSON can be a:928//929// - string containing JSON data like `{"fullname":"Bob","age":42}`930// - string containing a JSON filename, ending with ".json" (its931// content is [os.ReadFile] before unmarshaling)932// - []byte containing JSON data933// - [io.Reader] stream containing JSON data (is [io.ReadAll] before934// unmarshaling)935//936// JSON data contained in expectedJSON must be a JSON object/map937// (aka {…}) too. During a match, each expected entry should match in938// the compared map. But some entries in the compared map may not be939// expected.940//941// type MyStruct struct {942// Name string `json:"name"`943// Age int `json:"age"`944// City string `json:"city"`945// }946// got := MyStruct{947// Name: "Bob",948// Age: 42,949// City: "TestCity",950// }951// td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "age": 42}`)) // succeeds952// td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, miss "zip"953//954// expectedJSON JSON value can contain placeholders. The params are955// for any placeholder parameters in expectedJSON. params can contain956// [TestDeep] operators as well as raw values. A placeholder can be957// numeric like $2 or named like $name and always references an item958// in params.959//960// Numeric placeholders reference the n'th "operators" item (starting961// at 1). Named placeholders are used with [Tag] operator as follows:962//963// td.Cmp(t, gotValue,964// SuperJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,965// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name966// td.Between(41, 43), // matches only $2967// "male")) // matches only $3968//969// Note that placeholders can be double-quoted as in:970//971// td.Cmp(t, gotValue,972// td.SuperJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,973// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name974// td.Between(41, 43), // matches only $2975// "male")) // matches only $3976//977// It makes no difference whatever the underlying type of the replaced978// item is (= double quoting a placeholder matching a number is not a979// problem). It is just a matter of taste, double-quoting placeholders980// can be preferred when the JSON data has to conform to the JSON981// specification, like when used in a ".json" file.982//983// SuperJSONOf does its best to convert back the JSON corresponding to a984// placeholder to the type of the placeholder or, if the placeholder985// is an operator, to the type behind the operator. Allowing to do986// things like:987//988// td.Cmp(t, gotValue,989// td.SuperJSONOf(`{"foo":$1}`, []int{1, 2, 3, 4}))990// td.Cmp(t, gotValue,991// td.SuperJSONOf(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))992// td.Cmp(t, gotValue,993// td.SuperJSONOf(`{"foo":$1}`, td.Between(27, 32)))994//995// Of course, it does this conversion only if the expected type can be996// guessed. In the case the conversion cannot occur, data is compared997// as is, in its freshly unmarshaled JSON form (so as bool, float64,998// string, []any, map[string]any or simply nil).999//1000// Note expectedJSON can be a []byte, JSON filename or [io.Reader]:1001//1002// td.Cmp(t, gotValue, td.SuperJSONOf("file.json", td.Between(12, 34)))1003// td.Cmp(t, gotValue, td.SuperJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))1004// td.Cmp(t, gotValue, td.SuperJSONOf(osFile, td.Between(12, 34)))1005//1006// A JSON filename ends with ".json".1007//1008// To avoid a legit "$" string prefix causes a bad placeholder error,1009// just double it to escape it. Note it is only needed when the "$" is1010// the first character of a string:1011//1012// td.Cmp(t, gotValue,1013// td.SuperJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,1014// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name1015// td.Between(41, 43))) // matches only $21016//1017// For the "details" key, the raw value "$info" is expected, no1018// placeholders are involved here.1019//1020// Note that [Lax] mode is automatically enabled by SuperJSONOf operator to1021// simplify numeric tests.1022//1023// Comments can be embedded in JSON data:1024//1025// td.Cmp(t, gotValue,1026// td.SuperJSONOf(`1027// {1028// // A guy properties:1029// "fullname": "$name", // The full name of the guy1030// "details": "$$info", // Literally "$info", thanks to "$" escape1031// "age": $2 /* The age of the guy:1032// - placeholder unquoted, but could be without1033// any change1034// - to demonstrate a multi-lines comment */1035// }`,1036// td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name1037// td.Between(41, 43))) // matches only $21038//1039// Comments, like in go, have 2 forms. To quote the Go language specification:1040// - line comments start with the character sequence // and stop at the1041// end of the line.1042// - multi-lines comments start with the character sequence /* and stop1043// with the first subsequent character sequence */.1044//1045// Other JSON divergences:1046// - ',' can precede a '}' or a ']' (as in go);1047// - strings can contain non-escaped \n, \r and \t;1048// - raw strings are accepted (r{raw}, r!raw!, …), see below;1049// - int_lit & float_lit numbers as defined in go spec are accepted;1050// - numbers can be prefixed by '+'.1051//1052// Most operators can be directly embedded in SuperJSONOf without requiring1053// any placeholder. If an operators does not take any parameter, the1054// parenthesis can be omitted.1055//1056// td.Cmp(t, gotValue,1057// td.SuperJSONOf(`1058// {1059// "fullname": HasPrefix("Foo"),1060// "age": Between(41, 43),1061// "details": SuperMapOf({1062// "address": NotEmpty, // () are optional when no parameters1063// "car": Any("Peugeot", "Tesla", "Jeep") // any of these1064// })1065// }`))1066//1067// Placeholders can be used anywhere, even in operators parameters as in:1068//1069// td.Cmp(t, gotValue, td.SuperJSONOf(`{"fullname": HasPrefix($1)}`, "Zip"))1070//1071// A few notes about operators embedding:1072// - [SubMapOf] and [SuperMapOf] take only one parameter, a JSON object;1073// - the optional 3rd parameter of [Between] has to be specified as a string1074// and can be: "[]" or "BoundsInIn" (default), "[[" or "BoundsInOut",1075// "]]" or "BoundsOutIn", "][" or "BoundsOutOut";1076// - not all operators are embeddable only the following are: [All],1077// [Any], [ArrayEach], [Bag], [Between], [Contains],1078// [ContainsKey], [Empty], [First], [Grep], [Gt], [Gte],1079// [HasPrefix], [HasSuffix], [Ignore], [JSONPointer], [Keys],1080// [Last], [Len], [Lt], [Lte], [MapEach], [N], [NaN], [Nil],1081// [None], [Not], [NotAny], [NotEmpty], [NotNaN], [NotNil],1082// [NotZero], [Re], [ReAll], [Set], [SubBagOf], [SubMapOf],1083// [SubSetOf], [SuperBagOf], [SuperMapOf], [SuperSetOf], [Values]1084// and [Zero].1085//1086// It is also possible to embed operators in JSON strings. This way,1087// the JSON specification can be fulfilled. To avoid collision with1088// possible strings, just prefix the first operator name with1089// "$^". The previous example becomes:1090//1091// td.Cmp(t, gotValue,1092// td.SuperJSONOf(`1093// {1094// "fullname": "$^HasPrefix(\"Foo\")",1095// "age": "$^Between(41, 43)",1096// "details": "$^SuperMapOf({1097// \"address\": NotEmpty, // () are optional when no parameters1098// \"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these1099// })"1100// }`))1101//1102// As you can see, in this case, strings in strings have to be1103// escaped. Fortunately, newlines are accepted, but unfortunately they1104// are forbidden by JSON specification. To avoid too much escaping,1105// raw strings are accepted. A raw string is a "r" followed by a1106// delimiter, the corresponding delimiter closes the string. The1107// following raw strings are all the same as "foo\\bar(\"zip\")!":1108// - r'foo\bar"zip"!'1109// - r,foo\bar"zip"!,1110// - r%foo\bar"zip"!%1111// - r(foo\bar("zip")!)1112// - r{foo\bar("zip")!}1113// - r[foo\bar("zip")!]1114// - r<foo\bar("zip")!>1115//1116// So non-bracketing delimiters use the same character before and1117// after, but the 4 sorts of ASCII brackets (round, angle, square,1118// curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot1119// be escaped.1120//1121// With raw strings, the previous example becomes:1122//1123// td.Cmp(t, gotValue,1124// td.SuperJSONOf(`1125// {1126// "fullname": "$^HasPrefix(r<Foo>)",1127// "age": "$^Between(41, 43)",1128// "details": "$^SuperMapOf({1129// r<address>: NotEmpty, // () are optional when no parameters1130// r<car>: Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these1131// })"1132// }`))1133//1134// Note that raw strings are accepted anywhere, not only in original1135// JSON strings.1136//1137// To be complete, $^ can prefix an operator even outside a1138// string. This is accepted for compatibility purpose as the first1139// operator embedding feature used this way to embed some operators.1140//1141// So the following calls are all equivalent:1142//1143// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $1}`, td.NotZero()))1144// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero}`))1145// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero()}`))1146// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero}`))1147// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero()}`))1148// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero"}`))1149// td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero()"}`))1150//1151// As for placeholders, there is no differences between $^NotZero and1152// "$^NotZero".1153//1154// TypeBehind method returns the map[string]any type.1155//1156// See also [JSON], [JSONPointer] and [SubJSONOf].1157func SuperJSONOf(expectedJSON any, params ...any) TestDeep {1158 m := &tdMapJSON{1159 tdMap: tdMap{1160 tdExpectedType: tdExpectedType{1161 base: newBase(3),1162 expectedType: reflect.TypeOf((map[string]any)(nil)),1163 },1164 kind: superMap,1165 },1166 }1167 v, err := newJSONUnmarshaler(m.GetLocation()).unmarshal(expectedJSON, params)1168 if err != nil {1169 m.err = err1170 return m...

Full Screen

Full Screen

td_json_test.go

Source:td_json_test.go Github

copy

Full Screen

...697 }698 // Erroneous op699 equalTypes(t, td.JSON(`[`), nil)700}701func TestSubJSONOf(t *testing.T) {702 type MyStruct struct {703 Name string `json:"name"`704 Age uint `json:"age"`705 Gender string `json:"gender"`706 }707 //708 // struct709 //710 got := MyStruct{Name: "Bob", Age: 42, Gender: "male"}711 // No placeholder712 checkOK(t, got,713 td.SubJSONOf(`714{715 "name": "Bob",716 "age": 42,717 "gender": "male",718 "details": { // ← we don't want to test this field719 "city": "Test City",720 "zip": 666721 }722}`))723 // Numeric placeholders724 checkOK(t, got,725 td.SubJSONOf(`{"name":"$1","age":$2,"gender":$3,"details":{}}`,726 "Bob", 42, "male")) // raw values727 checkOK(t, got,728 td.SubJSONOf(`{"name":"$1","age":$2,"gender":$3,"details":{}}`,729 td.Re(`^Bob`),730 td.Between(40, 45),731 td.NotEmpty()))732 // Same using Flatten733 checkOK(t, got,734 td.SubJSONOf(`{"name":"$1","age":$2,"gender":$3,"details":{}}`,735 td.Re(`^Bob`),736 td.Flatten([]td.TestDeep{td.Between(40, 45), td.NotEmpty()}),737 ))738 // Tag placeholders739 checkOK(t, got,740 td.SubJSONOf(741 `{"name":"$name","age":$age,"gender":"$gender","details":{}}`,742 td.Tag("name", td.Re(`^Bob`)),743 td.Tag("age", td.Between(40, 45)),744 td.Tag("gender", td.NotEmpty())))745 // Mixed placeholders + operator746 for _, op := range []string{747 "NotEmpty",748 "NotEmpty()",749 "$^NotEmpty",750 "$^NotEmpty()",751 `"$^NotEmpty"`,752 `"$^NotEmpty()"`,753 `r<$^NotEmpty>`,754 `r<$^NotEmpty()>`,755 } {756 checkOK(t, got,757 td.SubJSONOf(758 `{"name":"$name","age":$1,"gender":`+op+`,"details":{}}`,759 td.Tag("age", td.Between(40, 45)),760 td.Tag("name", td.Re(`^Bob`))),761 "using operator %s", op)762 }763 //764 // Errors765 checkError(t, func() {}, td.SubJSONOf(`{}`),766 expectedError{767 Message: mustBe("json.Marshal failed"),768 Summary: mustContain("json: unsupported type"),769 })770 for i, n := range []any{771 nil,772 (map[string]any)(nil),773 (map[string]bool)(nil),774 ([]int)(nil),775 } {776 checkError(t, n, td.SubJSONOf(`{}`),777 expectedError{778 Message: mustBe("values differ"),779 Got: mustBe("null"),780 Expected: mustBe("non-null"),781 },782 "nil test #%d", i)783 }784 //785 // Fatal errors786 checkError(t, "never tested",787 td.SubJSONOf(`[1, "$123bad"]`),788 expectedError{789 Message: mustBe("bad usage of SubJSONOf operator"),790 Path: mustBe("DATA"),791 Summary: mustBe(`JSON unmarshal error: invalid numeric placeholder at line 1:5 (pos 5)`),792 })793 checkError(t, "never tested",794 td.SubJSONOf(`[1, $000]`),795 expectedError{796 Message: mustBe("bad usage of SubJSONOf operator"),797 Path: mustBe("DATA"),798 Summary: mustBe(`JSON unmarshal error: invalid numeric placeholder "$000", it should start at "$1" at line 1:4 (pos 4)`),799 })800 checkError(t, "never tested",801 td.SubJSONOf(`[1, $1]`),802 expectedError{803 Message: mustBe("bad usage of SubJSONOf operator"),804 Path: mustBe("DATA"),805 Summary: mustBe(`JSON unmarshal error: numeric placeholder "$1", but no params given at line 1:4 (pos 4)`),806 })807 checkError(t, "never tested",808 td.SubJSONOf(`[1, 2, $3]`, td.Ignore()),809 expectedError{810 Message: mustBe("bad usage of SubJSONOf operator"),811 Path: mustBe("DATA"),812 Summary: mustBe(`JSON unmarshal error: numeric placeholder "$3", but only one param given at line 1:7 (pos 7)`),813 })814 // $^Operator815 checkError(t, "never tested",816 td.SubJSONOf(`[1, $^bad%]`),817 expectedError{818 Message: mustBe("bad usage of SubJSONOf operator"),819 Path: mustBe("DATA"),820 Summary: mustBe(`JSON unmarshal error: $^ must be followed by an operator name at line 1:4 (pos 4)`),821 })822 checkError(t, "never tested",823 td.SubJSONOf(`[1, "$^bad%"]`),824 expectedError{825 Message: mustBe("bad usage of SubJSONOf operator"),826 Path: mustBe("DATA"),827 Summary: mustBe(`JSON unmarshal error: $^ must be followed by an operator name at line 1:5 (pos 5)`),828 })829 // named placeholders830 checkError(t, "never tested",831 td.SubJSONOf(`[1, "$bad%"]`),832 expectedError{833 Message: mustBe("bad usage of SubJSONOf operator"),834 Path: mustBe("DATA"),835 Summary: mustBe(`JSON unmarshal error: bad placeholder "$bad%" at line 1:5 (pos 5)`),836 })837 checkError(t, "never tested",838 td.SubJSONOf(`[1, $unknown]`),839 expectedError{840 Message: mustBe("bad usage of SubJSONOf operator"),841 Path: mustBe("DATA"),842 Summary: mustBe(`JSON unmarshal error: unknown placeholder "$unknown" at line 1:4 (pos 4)`),843 })844 checkError(t, "never tested",845 td.SubJSONOf("null"),846 expectedError{847 Message: mustBe("bad usage of SubJSONOf operator"),848 Path: mustBe("DATA"),849 Summary: mustBe("SubJSONOf() only accepts JSON objects {…}"),850 })851 //852 // Stringification853 test.EqualStr(t, td.SubJSONOf(`{}`).String(), `SubJSONOf({})`)854 test.EqualStr(t, td.SubJSONOf(`{"foo":1, "bar":2}`).String(),855 `856SubJSONOf({857 "bar": 2,858 "foo": 1859 })`[1:])860 test.EqualStr(t,861 td.SubJSONOf(`{"label": $value, "zip": $^NotZero}`,862 td.Tag("value", td.Bag(863 td.SubJSONOf(`{"name": $1,"age":$2}`,864 td.HasPrefix("Bob"),865 td.Between(12, 24),866 ),867 td.SubJSONOf(`{"name": $1}`, td.HasPrefix("Alice")),868 )),869 ).String(),870 `871SubJSONOf({872 "label": "$value" /* Bag(SubJSONOf({873 "age": "$2" /* 12 ≤ got ≤ 24 */,874 "name": "$1" /* HasPrefix("Bob") */875 }),876 SubJSONOf({877 "name": "$1" /* HasPrefix("Alice") */878 })) */,879 "zip": NotZero()880 })`[1:])881 // Erroneous op882 test.EqualStr(t, td.SubJSONOf(`123`).String(), "SubJSONOf(<ERROR>)")883}884func TestSubJSONOfTypeBehind(t *testing.T) {885 equalTypes(t, td.SubJSONOf(`{"a":12}`), (map[string]any)(nil))886 // Erroneous op887 equalTypes(t, td.SubJSONOf(`123`), nil)888}889func TestSuperJSONOf(t *testing.T) {890 type MyStruct struct {891 Name string `json:"name"`892 Age uint `json:"age"`893 Gender string `json:"gender"`894 Details string `json:"details"`895 }896 //897 // struct898 //899 got := MyStruct{Name: "Bob", Age: 42, Gender: "male", Details: "Nice"}900 // No placeholder901 checkOK(t, got, td.SuperJSONOf(`{"name": "Bob"}`))...

Full Screen

Full Screen

td_compat.go

Source:td_compat.go Github

copy

Full Screen

...152// CmpStruct is a deprecated alias of [td.CmpStruct].153var CmpStruct = td.CmpStruct154// CmpSubBagOf is a deprecated alias of [td.CmpSubBagOf].155var CmpSubBagOf = td.CmpSubBagOf156// CmpSubJSONOf is a deprecated alias of [td.CmpSubJSONOf].157var CmpSubJSONOf = td.CmpSubJSONOf158// CmpSubMapOf is a deprecated alias of [td.CmpSubMapOf].159var CmpSubMapOf = td.CmpSubMapOf160// CmpSubSetOf is a deprecated alias of [td.CmpSubSetOf].161var CmpSubSetOf = td.CmpSubSetOf162// CmpSuperBagOf is a deprecated alias of [td.CmpSuperBagOf].163var CmpSuperBagOf = td.CmpSuperBagOf164// CmpSuperJSONOf is a deprecated alias of [td.CmpSuperJSONOf].165var CmpSuperJSONOf = td.CmpSuperJSONOf166// CmpSuperMapOf is a deprecated alias of [td.CmpSuperMapOf].167var CmpSuperMapOf = td.CmpSuperMapOf168// CmpSuperSetOf is a deprecated alias of [td.CmpSuperSetOf].169var CmpSuperSetOf = td.CmpSuperSetOf170// CmpTruncTime is a deprecated alias of [td.CmpTruncTime].171var CmpTruncTime = td.CmpTruncTime172// CmpValues is a deprecated alias of [td.CmpValues].173var CmpValues = td.CmpValues174// CmpZero is a deprecated alias of [td.CmpZero].175var CmpZero = td.CmpZero176// All is a deprecated alias of [td.All].177var All = td.All178// Any is a deprecated alias of [td.Any].179var Any = td.Any180// Array is a deprecated alias of [td.Array].181var Array = td.Array182// ArrayEach is a deprecated alias of [td.ArrayEach].183var ArrayEach = td.ArrayEach184// Bag is a deprecated alias of [td.Bag].185var Bag = td.Bag186// Between is a deprecated alias of [td.Between].187var Between = td.Between188// Cap is a deprecated alias of [td.Cap].189var Cap = td.Cap190// Catch is a deprecated alias of [td.Catch].191var Catch = td.Catch192// Code is a deprecated alias of [td.Code].193var Code = td.Code194// Contains is a deprecated alias of [td.Contains].195var Contains = td.Contains196// ContainsKey is a deprecated alias of [td.ContainsKey].197var ContainsKey = td.ContainsKey198// Delay is a deprecated alias of [td.ContainsKey].199var Delay = td.Delay200// Empty is a deprecated alias of [td.Empty].201var Empty = td.Empty202// Gt is a deprecated alias of [td.Gt].203var Gt = td.Gt204// Gte is a deprecated alias of [td.Gte].205var Gte = td.Gte206// HasPrefix is a deprecated alias of [td.HasPrefix].207var HasPrefix = td.HasPrefix208// HasSuffix is a deprecated alias of [td.HasSuffix].209var HasSuffix = td.HasSuffix210// Ignore is a deprecated alias of [td.Ignore].211var Ignore = td.Ignore212// Isa is a deprecated alias of [td.Isa].213var Isa = td.Isa214// JSON is a deprecated alias of [td.JSON].215var JSON = td.JSON216// Keys is a deprecated alias of [td.Keys].217var Keys = td.Keys218// Lax is a deprecated alias of [td.Lax].219var Lax = td.Lax220// Len is a deprecated alias of [td.Len].221var Len = td.Len222// Lt is a deprecated alias of [td.Lt].223var Lt = td.Lt224// Lte is a deprecated alias of [td.Lte].225var Lte = td.Lte226// Map is a deprecated alias of [td.Map].227var Map = td.Map228// MapEach is a deprecated alias of [td.MapEach].229var MapEach = td.MapEach230// N is a deprecated alias of [td.N].231var N = td.N232// NaN is a deprecated alias of [td.NaN].233var NaN = td.NaN234// Nil is a deprecated alias of [td.Nil].235var Nil = td.Nil236// None is a deprecated alias of [td.None].237var None = td.None238// Not is a deprecated alias of [td.Not].239var Not = td.Not240// NotAny is a deprecated alias of [td.NotAny].241var NotAny = td.NotAny242// NotEmpty is a deprecated alias of [td.NotEmpty].243var NotEmpty = td.NotEmpty244// NotNaN is a deprecated alias of [td.NotNaN].245var NotNaN = td.NotNaN246// NotNil is a deprecated alias of [td.NotNil].247var NotNil = td.NotNil248// NotZero is a deprecated alias of [td.NotZero].249var NotZero = td.NotZero250// Ptr is a deprecated alias of [td.Ptr].251var Ptr = td.Ptr252// PPtr is a deprecated alias of [td.PPtr].253var PPtr = td.PPtr254// Re is a deprecated alias of [td.Re].255var Re = td.Re256// ReAll is a deprecated alias of [td.ReAll].257var ReAll = td.ReAll258// Set is a deprecated alias of [td.Set].259var Set = td.Set260// Shallow is a deprecated alias of [td.Shallow].261var Shallow = td.Shallow262// Slice is a deprecated alias of [td.Slice].263var Slice = td.Slice264// Smuggle is a deprecated alias of [td.Smuggle].265var Smuggle = td.Smuggle266// String is a deprecated alias of [td.String].267var String = td.String268// SStruct is a deprecated alias of [td.SStruct].269var SStruct = td.SStruct270// Struct is a deprecated alias of [td.Struct].271var Struct = td.Struct272// SubBagOf is a deprecated alias of [td.SubBagOf].273var SubBagOf = td.SubBagOf274// SubJSONOf is a deprecated alias of [td.SubJSONOf].275var SubJSONOf = td.SubJSONOf276// SubMapOf is a deprecated alias of [td.SubMapOf].277var SubMapOf = td.SubMapOf278// SubSetOf is a deprecated alias of [td.SubSetOf].279var SubSetOf = td.SubSetOf280// SuperBagOf is a deprecated alias of [td.SuperBagOf].281var SuperBagOf = td.SuperBagOf282// SuperJSONOf is a deprecated alias of [td.SuperJSONOf].283var SuperJSONOf = td.SuperJSONOf284// SuperMapOf is a deprecated alias of [td.SuperMapOf].285var SuperMapOf = td.SuperMapOf286// SuperSetOf is a deprecated alias of [td.SuperSetOf].287var SuperSetOf = td.SuperSetOf288// Tag is a deprecated alias of [td.Tag].289var Tag = td.Tag...

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 var json = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)4 var result = gjson.Get(string(json), "name")5 fmt.Println(result)6}7{"first":"Janet","last":"Prichard"}8import (9func main() {10 var json = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)11 var result = gjson.Get(string(json), "name.first")12 fmt.Println(result)13}14import (15func main() {16 var json = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)17 var result = gjson.Get(string(json), "age")18 fmt.Println(result)19}20import (21func main() {22 var json = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)23 var result = gjson.Get(string(json), "name.#")24 fmt.Println(result)25}26import (27func main() {28 var json = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 s := td.NewTestServer()4 defer s.Close()5 c := transport.NewTestClient(s)6 defer c.Close()7 c.Do("SET", "fleet", "truck1", "POINT", "33.5123", "-112.2693")8 c.Do("SET", "fleet", "truck2", "POINT", "33.462", "-112.2685")9 c.Do("SET", "fleet", "truck3", "POINT", "33.4505", "-112.1864")10 res := c.Do("GET", "fleet", "truck1")11 if res != `{"ok":true,"object":{"type":"Point","coordinates":[-112.2693,33.5123]}}`+"\r\n" {12 log.Panicf("invalid response: %s", res)13 }14 res = c.Do("GET", "fleet", "truck2")15 if res != `{"ok":true,"object":{"type":"Point","coordinates":[-112.2685,33.462]}}`+"\r\n" {16 log.Panicf("invalid response: %s", res)17 }18 res = c.Do("GET", "fleet", "truck3")19 if res != `{"ok":true,"object":{"type":"Point","coordinates":[-112.1864,33.4505]}}`+"\r\n" {20 log.Panicf("invalid response: %s", res)21 }22 res = c.Do("SUBJSONOF", "fleet", "truck1", "type")23 if res != `{"ok":true,"object":"Point"}`+"\r\n" {24 log.Panicf("invalid response: %s", res)25 }26 res = c.Do("SUBJSONOF", "fleet", "truck1", "coordinates")27 if res != `{"ok":true,"

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import "fmt"2import "encoding/json"3import "github.com/tidwall/gjson"4func main() {5 var jsonStr = []byte(`{"name":{"first":"Janet","last":"Prichard"},"age":47}`)6 var result = gjson.Get(string(jsonStr), "name")7 fmt.Println(result)8 fmt.Println(result.String())9 fmt.Println(result.Value())10 fmt.Println(result.Raw)11 fmt.Println(result.Type)12 fmt.Println(result.Exists())13 fmt.Println(result.Array())14 fmt.Println(result.Map())15 fmt.Println(result.Num)16 fmt.Println(result.Str)17 fmt.Println(result.Bool)18 fmt.Println(result.Index(0))19 fmt.Println(result.Get("first"))20 fmt.Println(result.Get("last"))21 fmt.Println(result.Get("age"))22 fmt.Println(result.Get("age").Num)23 fmt.Println(result.Get("age").Int())24 fmt.Println(result.Get("age").Uint())25 fmt.Println(result.Get("age").Float())26 fmt.Println(result.Get("age").String())27 fmt.Println(result.Get("age").Exists())28 fmt.Println(result.Get("age").Array())29 fmt.Println(result.Get("age").Map())30 fmt.Println(result.Get("age").Bool)31 fmt.Println(result.Get("age").Index(0))32 fmt.Println(result.Get("age").Get("first"))33 fmt.Println(result.Get("age").Get("last"))34 fmt.Println(result.Get("age").Get("age"))35 fmt.Println(result.Get("age").Get("age").Num)36 fmt.Println(result.Get("age").Get("age").Int())37 fmt.Println(result.Get("age").Get("age").Uint())38 fmt.Println(result.Get("age").Get("age").Float())39 fmt.Println(result.Get("age").Get("age").String())40 fmt.Println(result.Get("age").Get("age").Exists())41 fmt.Println(result.Get("age").Get("age").Array())42 fmt.Println(result.Get("age").Get("age").Map())43 fmt.Println(result.Get("age").Get("age").Bool)44 fmt.Println(result.Get("age").Get("age").Index(0))45 fmt.Println(result.Get("age").Get("age").Get("first"))46 fmt.Println(result.Get("age").Get("age").Get("last"))47 fmt.Println(result.Get("age").Get("age").Get("age"))48 fmt.Println(result.Get("age

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 jsonData := []byte(`{4 "person": {5 }6 }`)7 jsonPath := []string{"person", "hobbies"}8 subJSON, _, _, err := jsonparser.Get(jsonData, jsonPath...)9 if err != nil {10 fmt.Println("Error while getting sub json of json path:", err)11 }12 fmt.Println("Sub json of json path:", string(subJSON))13 subJSON, err = jsonparser.SubJSONOf(jsonData, jsonPath...)14 if err != nil {15 fmt.Println("Error while getting sub json of json path:", err)16 }17 fmt.Println("Sub json of json path:", string(subJSON))18}19import (20func main() {21 jsonData := []byte(`{22 "person": {23 }24 }`)25 jsonPath := []string{"person", "hobbies"}26 subJSON, _, _, err := jsonparser.Get(jsonData, jsonPath...)27 if err != nil {28 fmt.Println("Error while getting sub json of json path:", err)29 }30 fmt.Println("Sub json of json path:", string(subJSON))31 subJSON, err = jsonparser.SubJSON(jsonData, jsonPath...)32 if err != nil {33 fmt.Println("Error while getting sub json of json path:", err)34 }

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 json := []byte(`{"name":"John", "age":20, "cars": {"car1":"Ford", "car2":"BMW", "car3":"Fiat"}}`)4 value, _, _, err := jsonparser.Get(json, "cars")5 if err != nil {6 fmt.Println(err)7 }8 jsonparser.SubJSONOf(value, "car1")9}10func (td *TData) String(keys ...string) string11import (12func main() {13 json := []byte(`{"name":"John", "age":20, "cars": {"car1":"Ford", "car2":"BMW", "car3":"Fiat"}}`)14 value, _, _, err := jsonparser.Get(json, "cars")15 if err != nil {16 fmt.Println(err)17 }18 jsonparser.String(value, "car1")19}20import (21func main() {22 json := []byte(`{"name":"John", "age":20, "cars": {"car1":"Ford", "car2":"BMW", "car3":"Fiat"}}`)23 value, _, _, err := jsonparser.Get(json, "cars")24 if err != nil {25 fmt.Println(err)26 }27 jsonparser.String(value, "car1")28}

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 var td1 = `{"name": "John", "age": 20, "cars": [{"name": "Ford", "models": ["Fiesta", "Focus", "Mustang"]}, {"name": "BMW", "models": ["320", "X3", "X5"]}, {"name": "Fiat", "models": ["500", "Panda"]}], "address": {"street": "Main street", "number": 123}}`4 var td2 = `{"name": "John", "age": 20, "cars": [{"name": "Ford", "models": ["Fiesta", "Focus", "Mustang"]}, {"name": "BMW", "models": ["320", "X3", "X5"]}, {"name": "Fiat", "models": ["500", "Panda"]}], "address": {"street": "Main street", "number": 123}}`5 var td3 = `{"name": "John", "age": 20, "cars": [{"name": "Ford", "models": ["Fiesta", "Focus", "Mustang"]}, {"name": "BMW", "models": ["320", "X3", "X5"]}, {"name": "Fiat", "models": ["500", "Panda"]}], "address": {"street": "Main street", "number": 123}}`6 var td4 = `{"name": "John", "age": 20, "cars": [{"name": "Ford", "models": ["Fiesta", "Focus", "Mustang"]}, {"name": "BMW", "models": ["320", "X3", "X5"]}, {"name": "Fiat", "models": ["500", "Panda"]}], "address": {"street": "Main street", "number": 123}}`7 var td5 = `{"name": "John", "age": 20, "cars": [{"name": "Ford", "models": ["Fiesta", "Focus", "Mustang"]}, {"name": "BMW", "models": ["320", "X3", "X5"]}, {"name": "Fiat", "models": ["500", "Panda"]}], "address": {"street": "Main

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 td = Testdata{4 []string{"a", "b", "c"},5 }6 b, err := td.SubJSONOf("test")7 if err != nil {8 fmt.Println(err)9 }10 fmt.Println(string(b))11}12import (13func main() {14 td = Testdata{15 []string{"a", "b", "c"},16 }17 b, err := td.SubJSONOf("test")18 if err != nil {19 fmt.Println(err)20 }21 fmt.Println(string(b))22}23{ 24 "test":{ 25 "a":{ 26 "b":{ 27 "c":{ 28 }29 }30 }31 }32}33type Test struct {34}35{ 36 "test":{ 37 "a":{ 38 "b":{ 39 "c":{ 40 }41 }42 }43 }44}45import (46type Test struct {47}48func main() {49 t := Test{50 }51 b, _ := json.Marshal(t)52 fmt.Println(string(b))53}54{"A":"a","B":"b","C":"c"}55{ 56 "test":{ 57 "a":{ 58 "b":{ 59 "c":{ 60 }61 }62 }63 }64}65import (66type Test struct {67}68func (t Test) MarshalJSON() ([]byte, error)

Full Screen

Full Screen

SubJSONOf

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 geojson := geojson.NewObject()4 geojson.AddPoint("point", 1, 2)5 geojson.AddLineString("line", 1, 2, 3, 4, 5, 6)6 geojson.AddPolygon("polygon", 1, 2, 3, 4, 5, 6, 1, 2)7 geojson.AddFeature("feature", 1, 2)8 geojson.AddFeatureCollection("featurecollection", 1, 2)9 geojson.AddGeometryCollection("geometrycollection", 1, 2)10 subjson := geojson.NewObject()11 subjson.AddPoint("point", 1, 2)12 subjson.AddLineString("line", 1, 2, 3, 4, 5, 6)13 subjson.AddPolygon("polygon", 1, 2, 3, 4, 5, 6, 1, 2)14 subjson.AddFeature("feature", 1, 2)15 subjson.AddFeatureCollection("featurecollection", 1, 2)16 subjson.AddGeometryCollection("geometrycollection", 1, 2)17 geojson.AddSubJSON("subjson", subjson)18 fmt.Println(geojson.String())19}20{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{"point":true}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1,2],[3,4],[5,6]]},"properties":{"line

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Go-testdeep automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Most used method in

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful