| // Copyright 2010 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 | |
| package json | |
| import ( | |
| "errors" | |
| "io" | |
| "strings" | |
| "encoding/json/internal" | |
| "encoding/json/internal/jsonflags" | |
| "encoding/json/jsontext" | |
| ) | |
| // export exposes internal functionality of the "jsontext" package. | |
| var export = jsontext.Internal.Export(&internal.AllowInternalUse) | |
| // Valid reports whether data is a valid JSON encoding. | |
| func Valid(data []byte) bool { | |
| return checkValid(data) == nil | |
| } | |
| func checkValid(data []byte) error { | |
| d := export.GetBufferedDecoder(data) | |
| defer export.PutBufferedDecoder(d) | |
| xd := export.Decoder(d) | |
| xd.Struct.Flags.Set(jsonflags.ReportErrorsWithLegacySemantics | jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1) | |
| if _, err := d.ReadValue(); err != nil { | |
| if err == io.EOF { | |
| offset := d.InputOffset() + int64(len(d.UnreadBuffer())) | |
| err = &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF} | |
| } | |
| return transformSyntacticError(err) | |
| } | |
| if err := xd.CheckEOF(); err != nil { | |
| return transformSyntacticError(err) | |
| } | |
| return nil | |
| } | |
| // A SyntaxError is a description of a JSON syntax error. | |
| // [Unmarshal] will return a SyntaxError if the JSON can't be parsed. | |
| type SyntaxError struct { | |
| msg string // description of error | |
| Offset int64 // error occurred after reading Offset bytes | |
| } | |
| func (e *SyntaxError) Error() string { return e.msg } | |
| var errUnexpectedEnd = errors.New("unexpected end of JSON input") | |
| func transformSyntacticError(err error) error { | |
| switch serr, ok := err.(*jsontext.SyntacticError); { | |
| case serr != nil: | |
| // If the SyntacticError wraps an IO error, unwrap it | |
| // to match v1 behavior which returned IO errors directly. | |
| if export.IsIOError(serr.Err) { | |
| return errors.Unwrap(serr.Err) | |
| } | |
| if serr.Err == io.ErrUnexpectedEOF { | |
| serr.Err = errUnexpectedEnd | |
| } | |
| msg := serr.Err.Error() | |
| if i := strings.Index(msg, " (expecting"); i >= 0 && !strings.Contains(msg, " in literal") { | |
| msg = msg[:i] | |
| } | |
| return &SyntaxError{Offset: serr.ByteOffset, msg: syntaxErrorReplacer.Replace(msg)} | |
| case ok: | |
| return (*SyntaxError)(nil) | |
| case export.IsIOError(err): | |
| return errors.Unwrap(err) // v1 historically did not wrap IO errors | |
| default: | |
| return err | |
| } | |
| } | |
| // syntaxErrorReplacer replaces certain string literals in the v2 error | |
| // to better match the historical string rendering of syntax errors. | |
| // In particular, v2 uses the terminology "object name" to match RFC 8259, | |
| // while v1 uses "object key", which is not a term found in JSON literature. | |
| var syntaxErrorReplacer = strings.NewReplacer( | |
| "object name", "object key", | |
| "at start of value", "looking for beginning of value", | |
| "at start of string", "looking for beginning of object key string", | |
| "after object value", "after object key:value pair", | |
| "in number", "in numeric literal", | |
| ) |