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