github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/json/noms_json_value.go (about) 1 // Copyright 2021 Dolthub, Inc. 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 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "sort" 22 "strings" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 gmstypes "github.com/dolthub/go-mysql-server/sql/types" 26 27 "github.com/dolthub/dolt/go/store/types" 28 ) 29 30 var ErrUnexpectedJSONTypeIn = errors.New("unexpected type during JSON marshalling") 31 var ErrUnexpectedJSONTypeOut = errors.New("unexpected type during JSON unmarshalling") 32 33 const ( 34 JSONNull = "null" 35 ) 36 37 // NomsJSON is a type alias for types.JSON. The alias allows MySQL-specific 38 // logic to be kept separate from the storage-layer code in pkg types. 39 type NomsJSON types.JSON 40 41 var _ sql.JSONWrapper = NomsJSON{} 42 43 // NomsJSONFromJSONValue converts a sql.JSONValue to a NomsJSON value. 44 func NomsJSONFromJSONValue(ctx context.Context, vrw types.ValueReadWriter, val sql.JSONWrapper) (NomsJSON, error) { 45 if noms, ok := val.(NomsJSON); ok { 46 return noms, nil 47 } 48 49 sqlVal, err := val.ToInterface() 50 if err != nil { 51 return NomsJSON{}, err 52 } 53 54 v, err := marshalJSON(ctx, vrw, sqlVal) 55 if err != nil { 56 return NomsJSON{}, err 57 } 58 59 doc, err := types.NewJSONDoc(vrw.Format(), vrw, v) 60 if err != nil { 61 return NomsJSON{}, err 62 } 63 64 return NomsJSON(doc), nil 65 } 66 67 func marshalJSON(ctx context.Context, vrw types.ValueReadWriter, val interface{}) (types.Value, error) { 68 if val == nil { 69 return types.NullValue, nil 70 } 71 72 switch val := val.(type) { 73 case []interface{}: 74 return marshalJSONArray(ctx, vrw, val) 75 case map[string]interface{}: 76 return marshalJSONObject(ctx, vrw, val) 77 case bool: 78 return types.Bool(val), nil 79 case string: 80 return types.String(val), nil 81 case float64: 82 return types.Float(val), nil 83 84 // TODO(andy): unclear how to handle these 85 case float32: 86 return types.Float(val), nil 87 case int: 88 return types.Float(val), nil 89 case int8: 90 return types.Float(val), nil 91 case int16: 92 return types.Float(val), nil 93 case int32: 94 return types.Float(val), nil 95 case int64: 96 return types.Float(val), nil 97 case uint: 98 return types.Float(val), nil 99 case uint8: 100 return types.Float(val), nil 101 case uint16: 102 return types.Float(val), nil 103 case uint32: 104 return types.Float(val), nil 105 case uint64: 106 return types.Float(val), nil 107 default: 108 return nil, ErrUnexpectedJSONTypeIn 109 } 110 } 111 112 func marshalJSONArray(ctx context.Context, vrw types.ValueReadWriter, arr []interface{}) (types.Value, error) { 113 var err error 114 vals := make([]types.Value, len(arr)) 115 for i, elem := range arr { 116 vals[i], err = marshalJSON(ctx, vrw, elem) 117 if err != nil { 118 return nil, err 119 } 120 } 121 return types.NewList(ctx, vrw, vals...) 122 } 123 124 func marshalJSONObject(ctx context.Context, vrw types.ValueReadWriter, obj map[string]interface{}) (types.Value, error) { 125 var err error 126 i := 0 127 vals := make([]types.Value, len(obj)*2) 128 for k, v := range obj { 129 vals[i] = types.String(k) 130 vals[i+1], err = marshalJSON(ctx, vrw, v) 131 if err != nil { 132 return nil, err 133 } 134 i += 2 135 } 136 return types.NewMap(ctx, vrw, vals...) 137 } 138 139 func (v NomsJSON) ToInterface() (interface{}, error) { 140 nomsVal, err := types.JSON(v).Inner() 141 if err != nil { 142 return nil, err 143 } 144 145 val, err := unmarshalJSON(context.Background(), nomsVal) 146 if err != nil { 147 return nil, err 148 } 149 return val, nil 150 } 151 152 // Unmarshall implements the sql.JSONValue interface. 153 func (v NomsJSON) Unmarshall(ctx *sql.Context) (doc gmstypes.JSONDocument, err error) { 154 nomsVal, err := types.JSON(v).Inner() 155 if err != nil { 156 return gmstypes.JSONDocument{}, err 157 } 158 159 val, err := unmarshalJSON(ctx, nomsVal) 160 if err != nil { 161 return gmstypes.JSONDocument{}, err 162 } 163 164 return gmstypes.JSONDocument{Val: val}, nil 165 } 166 167 func unmarshalJSON(ctx context.Context, val types.Value) (interface{}, error) { 168 switch val := val.(type) { 169 case types.Null: 170 return nil, nil 171 case types.Bool: 172 return bool(val), nil 173 case types.String: 174 return string(val), nil 175 case types.Float: 176 return float64(val), nil 177 case types.List: 178 return unmarshalJSONArray(ctx, val) 179 case types.Map: 180 return unmarshalJSONObject(ctx, val) 181 default: 182 return nil, ErrUnexpectedJSONTypeIn 183 } 184 } 185 186 func unmarshalJSONArray(ctx context.Context, l types.List) (arr []interface{}, err error) { 187 arr = make([]interface{}, l.Len()) 188 err = l.Iter(ctx, func(v types.Value, index uint64) (stop bool, err error) { 189 arr[index], err = unmarshalJSON(ctx, v) 190 return 191 }) 192 return 193 } 194 195 func unmarshalJSONObject(ctx context.Context, m types.Map) (obj map[string]interface{}, err error) { 196 obj = make(map[string]interface{}, m.Len()) 197 err = m.Iter(ctx, func(key, value types.Value) (stop bool, err error) { 198 ks, ok := key.(types.String) 199 if !ok { 200 return false, ErrUnexpectedJSONTypeOut 201 } 202 203 obj[string(ks)], err = unmarshalJSON(ctx, value) 204 return 205 }) 206 return 207 } 208 209 // JSONString implements the sql.JSONWrapper interface. 210 func (v NomsJSON) JSONString() (string, error) { 211 return NomsJSONToString(context.Background(), v) 212 } 213 214 func NomsJSONToString(ctx context.Context, js NomsJSON) (string, error) { 215 jd, err := types.JSON(js).Inner() 216 if err != nil { 217 return "", err 218 } 219 220 sb := &strings.Builder{} 221 if err = marshalToString(ctx, sb, jd); err != nil { 222 return "", err 223 } 224 225 return sb.String(), nil 226 } 227 228 func marshalToString(ctx context.Context, sb *strings.Builder, val types.Value) (err error) { 229 switch val := val.(type) { 230 case types.Null: 231 sb.WriteString(JSONNull) 232 233 case types.Bool: 234 sb.WriteString(val.HumanReadableString()) 235 236 case types.String: 237 sb.WriteString(val.HumanReadableString()) 238 239 case types.Float: 240 sb.WriteString(val.HumanReadableString()) 241 242 case types.List: 243 sb.WriteRune('[') 244 seenOne := false 245 err = val.Iter(ctx, func(v types.Value, _ uint64) (stop bool, err error) { 246 if seenOne { 247 sb.WriteString(", ") 248 } 249 seenOne = true 250 err = marshalToString(ctx, sb, v) 251 return 252 }) 253 if err != nil { 254 return err 255 } 256 sb.WriteRune(']') 257 258 case types.Map: 259 obj := make(map[string]types.Value, val.Len()) 260 var keys []string 261 err = val.Iter(ctx, func(key, value types.Value) (stop bool, err error) { 262 ks, ok := key.(types.String) 263 if !ok { 264 return false, ErrUnexpectedJSONTypeOut 265 } 266 obj[string(ks)] = value 267 keys = append(keys, string(ks)) 268 return 269 }) 270 if err != nil { 271 return err 272 } 273 // JSON map keys are sorted k by length then alphabetically 274 sort.Slice(keys, func(i, j int) bool { 275 if len(keys[i]) != len(keys[j]) { 276 return len(keys[i]) < len(keys[j]) 277 } 278 return keys[i] < keys[j] 279 }) 280 281 sb.WriteRune('{') 282 seenOne := false 283 for _, k := range keys { 284 if seenOne { 285 sb.WriteString(", ") 286 } 287 seenOne = true 288 sb.WriteString(fmt.Sprintf("\"%s\": ", k)) 289 err = marshalToString(ctx, sb, obj[k]) 290 if err != nil { 291 return err 292 } 293 } 294 sb.WriteRune('}') 295 default: 296 err = ErrUnexpectedJSONTypeOut 297 } 298 return 299 }