cuelang.org/go@v0.10.1/encoding/json/json.go (about) 1 // Copyright 2019 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package json converts JSON to and from CUE. 16 package json 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "fmt" 22 "io" 23 "strings" 24 25 "cuelang.org/go/cue" 26 "cuelang.org/go/cue/ast" 27 "cuelang.org/go/cue/ast/astutil" 28 "cuelang.org/go/cue/errors" 29 "cuelang.org/go/cue/literal" 30 "cuelang.org/go/cue/parser" 31 "cuelang.org/go/cue/token" 32 "cuelang.org/go/internal/source" 33 ) 34 35 // Valid reports whether data is a valid JSON encoding. 36 func Valid(b []byte) bool { 37 return json.Valid(b) 38 } 39 40 // Validate validates JSON and confirms it matches the constraints 41 // specified by v. 42 func Validate(b []byte, v cue.Value) error { 43 if !json.Valid(b) { 44 return fmt.Errorf("json: invalid JSON") 45 } 46 v2 := v.Context().CompileBytes(b, cue.Filename("json.Validate")) 47 if err := v2.Err(); err != nil { 48 return err 49 } 50 51 v = v.Unify(v2) 52 if err := v.Err(); err != nil { 53 return err 54 } 55 return v.Validate(cue.Final()) 56 } 57 58 // Extract parses JSON-encoded data to a CUE expression, using path for 59 // position information. 60 func Extract(path string, data []byte) (ast.Expr, error) { 61 expr, err := extract(path, data) 62 if err != nil { 63 return nil, err 64 } 65 patchExpr(expr, nil) 66 return expr, nil 67 } 68 69 // Decode parses JSON-encoded data to a CUE value, using path for position 70 // information. 71 // 72 // Deprecated: use Extract and build using cue.Context.BuildExpr. 73 func Decode(r *cue.Runtime, path string, data []byte) (*cue.Instance, error) { 74 expr, err := extract(path, data) 75 if err != nil { 76 return nil, err 77 } 78 return r.CompileExpr(expr) 79 } 80 81 func extract(path string, b []byte) (ast.Expr, error) { 82 expr, err := parser.ParseExpr(path, b) 83 if err != nil || !json.Valid(b) { 84 p := token.NoPos 85 if pos := errors.Positions(err); len(pos) > 0 { 86 p = pos[0] 87 } 88 var x interface{} 89 err := json.Unmarshal(b, &x) 90 91 // If encoding/json has a position, prefer that, as it relates to json.Unmarshal's error message. 92 if synErr, ok := err.(*json.SyntaxError); ok && len(b) > 0 { 93 tokFile := token.NewFile(path, 0, len(b)) 94 tokFile.SetLinesForContent(b) 95 p = tokFile.Pos(int(synErr.Offset-1), token.NoRelPos) 96 } 97 98 return nil, errors.Wrapf(err, p, "invalid JSON for file %q", path) 99 } 100 return expr, nil 101 } 102 103 // NewDecoder configures a JSON decoder. The path is used to associate position 104 // information with each node. The runtime may be nil if the decoder 105 // is only used to extract to CUE ast objects. 106 // 107 // The runtime may be nil if Decode isn't used. 108 func NewDecoder(r *cue.Runtime, path string, src io.Reader) *Decoder { 109 b, err := source.ReadAll(path, src) 110 tokFile := token.NewFile(path, 0, len(b)) 111 tokFile.SetLinesForContent(b) 112 return &Decoder{ 113 r: r, 114 path: path, 115 dec: json.NewDecoder(bytes.NewReader(b)), 116 tokFile: tokFile, 117 readAllErr: err, 118 } 119 } 120 121 // A Decoder converts JSON values to CUE. 122 type Decoder struct { 123 r *cue.Runtime 124 path string 125 dec *json.Decoder 126 127 startOffset int 128 tokFile *token.File 129 readAllErr error 130 } 131 132 // Extract converts the current JSON value to a CUE ast. It returns io.EOF 133 // if the input has been exhausted. 134 func (d *Decoder) Extract() (ast.Expr, error) { 135 if d.readAllErr != nil { 136 return nil, d.readAllErr 137 } 138 139 expr, err := d.extract() 140 if err != nil { 141 return expr, err 142 } 143 patchExpr(expr, d.patchPos) 144 return expr, nil 145 } 146 147 func (d *Decoder) extract() (ast.Expr, error) { 148 var raw json.RawMessage 149 err := d.dec.Decode(&raw) 150 if err == io.EOF { 151 return nil, err 152 } 153 if err != nil { 154 pos := token.NoPos 155 // When decoding into a RawMessage, encoding/json should only error due to syntax errors. 156 if synErr, ok := err.(*json.SyntaxError); ok { 157 pos = d.tokFile.Pos(int(synErr.Offset-1), token.NoRelPos) 158 } 159 return nil, errors.Wrapf(err, pos, "invalid JSON for file %q", d.path) 160 } 161 expr, err := parser.ParseExpr(d.path, []byte(raw)) 162 if err != nil { 163 return nil, err 164 } 165 166 d.startOffset = int(d.dec.InputOffset()) - len(raw) 167 return expr, nil 168 } 169 170 func (d *Decoder) patchPos(n ast.Node) { 171 pos := n.Pos() 172 realPos := d.tokFile.Pos(pos.Offset()+d.startOffset, pos.RelPos()) 173 ast.SetPos(n, realPos) 174 } 175 176 // Decode converts the current JSON value to a CUE instance. It returns io.EOF 177 // if the input has been exhausted. 178 // 179 // Deprecated: use Extract and build with cue.Context.BuildExpr. 180 func (d *Decoder) Decode() (*cue.Instance, error) { 181 expr, err := d.Extract() 182 if err != nil { 183 return nil, err 184 } 185 return d.r.CompileExpr(expr) 186 } 187 188 // patchExpr simplifies the AST parsed from JSON. 189 // TODO: some of the modifications are already done in format, but are 190 // a package deal of a more aggressive simplify. Other pieces of modification 191 // should probably be moved to format. 192 func patchExpr(n ast.Node, patchPos func(n ast.Node)) { 193 type info struct { 194 reflow bool 195 } 196 stack := []info{{true}} 197 198 afterFn := func(n ast.Node) { 199 switch n.(type) { 200 case *ast.ListLit, *ast.StructLit: 201 stack = stack[:len(stack)-1] 202 } 203 } 204 205 var beforeFn func(n ast.Node) bool 206 207 beforeFn = func(n ast.Node) bool { 208 if patchPos != nil { 209 patchPos(n) 210 } 211 212 isLarge := n.End().Offset()-n.Pos().Offset() > 50 213 descent := true 214 215 switch x := n.(type) { 216 case *ast.ListLit: 217 reflow := true 218 if !isLarge { 219 for _, e := range x.Elts { 220 if hasSpaces(e) { 221 reflow = false 222 break 223 } 224 } 225 } 226 stack = append(stack, info{reflow}) 227 if reflow { 228 x.Lbrack = x.Lbrack.WithRel(token.NoRelPos) 229 x.Rbrack = x.Rbrack.WithRel(token.NoRelPos) 230 } 231 return true 232 233 case *ast.StructLit: 234 reflow := true 235 if !isLarge { 236 for _, e := range x.Elts { 237 if f, ok := e.(*ast.Field); !ok || hasSpaces(f) || hasSpaces(f.Value) { 238 reflow = false 239 break 240 } 241 } 242 } 243 stack = append(stack, info{reflow}) 244 if reflow { 245 x.Lbrace = x.Lbrace.WithRel(token.NoRelPos) 246 x.Rbrace = x.Rbrace.WithRel(token.NoRelPos) 247 } 248 return true 249 250 case *ast.Field: 251 // label is always a string for JSON. 252 switch { 253 case true: 254 s, ok := x.Label.(*ast.BasicLit) 255 if !ok || s.Kind != token.STRING { 256 break // should not happen: implies invalid JSON 257 } 258 259 u, err := literal.Unquote(s.Value) 260 if err != nil { 261 break // should not happen: implies invalid JSON 262 } 263 264 // TODO(legacy): remove checking for '_' prefix once hidden 265 // fields are removed. 266 if !ast.IsValidIdent(u) || strings.HasPrefix(u, "_") { 267 break // keep string 268 } 269 270 x.Label = ast.NewIdent(u) 271 astutil.CopyMeta(x.Label, s) 272 } 273 ast.Walk(x.Value, beforeFn, afterFn) 274 descent = false 275 276 case *ast.BasicLit: 277 if x.Kind == token.STRING && len(x.Value) > 10 { 278 s, err := literal.Unquote(x.Value) 279 if err != nil { 280 break // should not happen: implies invalid JSON 281 } 282 283 x.Value = literal.String.WithOptionalTabIndent(len(stack)).Quote(s) 284 } 285 } 286 287 if stack[len(stack)-1].reflow { 288 ast.SetRelPos(n, token.NoRelPos) 289 } 290 return descent 291 } 292 293 ast.Walk(n, beforeFn, afterFn) 294 } 295 296 func hasSpaces(n ast.Node) bool { 297 return n.Pos().RelPos() > token.NoSpace 298 }