mirror of https://github.com/kubernetes/kops.git
				
				
				
			
		
			
				
	
	
		
			185 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package jsonutils
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // JSONStreamWriter writes tokens as parsed by a json.Decoder back to a string
 | |
| type JSONStreamWriter struct {
 | |
| 	// out is the output destination
 | |
| 	out io.Writer
 | |
| 
 | |
| 	// indent is the current indent level
 | |
| 	indent string
 | |
| 
 | |
| 	// state stores a stack of the json state, comprised of [ { and F characters.  F=field
 | |
| 	state string
 | |
| 
 | |
| 	// deferred is used to buffer the output temporarily, used to prevent a trailing comma in an object
 | |
| 	deferred string
 | |
| 
 | |
| 	// path is the current stack of fields, used to support the Path() function
 | |
| 	path []string
 | |
| }
 | |
| 
 | |
| // NewJSONStreamWriter is the constructor for a JSONStreamWriter
 | |
| func NewJSONStreamWriter(out io.Writer) *JSONStreamWriter {
 | |
| 	return &JSONStreamWriter{
 | |
| 		out: out,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Path returns the path to the current position in the JSON tree
 | |
| func (j *JSONStreamWriter) Path() string {
 | |
| 	return strings.Join(j.path, ".")
 | |
| }
 | |
| 
 | |
| // WriteToken writes the next token to the output
 | |
| func (j *JSONStreamWriter) WriteToken(token json.Token) error {
 | |
| 	state := byte(0)
 | |
| 	if j.state != "" {
 | |
| 		state = j.state[len(j.state)-1]
 | |
| 	}
 | |
| 
 | |
| 	var v string
 | |
| 	switch tt := token.(type) {
 | |
| 	//	Delim, for the four JSON delimiters [ ] { }
 | |
| 	case json.Delim:
 | |
| 		v = tt.String()
 | |
| 		indent := j.indent
 | |
| 		switch tt {
 | |
| 		case json.Delim('{'):
 | |
| 			j.indent += "  "
 | |
| 			j.state += "{"
 | |
| 		case json.Delim('['):
 | |
| 			j.indent += "  "
 | |
| 			j.state += "["
 | |
| 		case json.Delim(']'), json.Delim('}'):
 | |
| 			j.indent = j.indent[:len(j.indent)-2]
 | |
| 			indent = j.indent
 | |
| 			j.state = j.state[:len(j.state)-1]
 | |
| 			if j.state != "" && j.state[len(j.state)-1] == 'F' {
 | |
| 				j.state = j.state[:len(j.state)-1]
 | |
| 				j.path = j.path[:len(j.path)-1]
 | |
| 			}
 | |
| 			// Don't put a comma on the last field in a block
 | |
| 			if j.deferred == ",\n" {
 | |
| 				j.deferred = "\n"
 | |
| 			}
 | |
| 		default:
 | |
| 			return fmt.Errorf("unknown delim: %v", tt)
 | |
| 		}
 | |
| 
 | |
| 		switch state {
 | |
| 		case 0:
 | |
| 			if err := j.writeRaw(indent + v); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		case '{':
 | |
| 			if err := j.writeRaw(indent + v); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		case '[':
 | |
| 			if err := j.writeRaw(indent + v); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		case 'F':
 | |
| 			if err := j.writeRaw(v); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 		default:
 | |
| 			return fmt.Errorf("unhandled state for json delim serialization: %v %q", state, j.state)
 | |
| 		}
 | |
| 
 | |
| 		switch tt {
 | |
| 		case json.Delim('{'):
 | |
| 			j.deferred = "\n"
 | |
| 		case json.Delim('['):
 | |
| 			j.deferred = "\n"
 | |
| 		case json.Delim(']'), json.Delim('}'):
 | |
| 			j.deferred = ",\n"
 | |
| 		default:
 | |
| 			return fmt.Errorf("unknown delim: %v", tt)
 | |
| 		}
 | |
| 
 | |
| 		return nil
 | |
| 
 | |
| 		//		bool, for JSON booleans
 | |
| 	case bool:
 | |
| 		v = fmt.Sprintf("%v", tt)
 | |
| 
 | |
| 		//	string, for JSON string literals
 | |
| 	case string:
 | |
| 		v = "\"" + tt + "\""
 | |
| 
 | |
| 		//	float64, for JSON numbers
 | |
| 	case float64:
 | |
| 		v = fmt.Sprintf("%g", tt)
 | |
| 
 | |
| 		//	Number, for JSON numbers
 | |
| 	case json.Number:
 | |
| 		v = tt.String()
 | |
| 
 | |
| 		//	nil, for JSON null
 | |
| 	case nil:
 | |
| 		v = "null"
 | |
| 
 | |
| 	default:
 | |
| 		return fmt.Errorf("unhandled token type %T", tt)
 | |
| 	}
 | |
| 
 | |
| 	switch state {
 | |
| 	case '{':
 | |
| 		j.state += "F"
 | |
| 		j.path = append(j.path, fmt.Sprintf("%s", token))
 | |
| 		return j.writeRaw(j.indent + v + ": ")
 | |
| 	case '[':
 | |
| 		if err := j.writeRaw(j.indent + v); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		j.deferred = ",\n"
 | |
| 		return nil
 | |
| 	case 'F':
 | |
| 		j.state = j.state[:len(j.state)-1]
 | |
| 		j.path = j.path[:len(j.path)-1]
 | |
| 		if err := j.writeRaw(v); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		j.deferred = ",\n"
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("unhandled state for json value (%T %q) serialization: %v %q", token, v, state, j.state)
 | |
| }
 | |
| 
 | |
| func (j *JSONStreamWriter) writeRaw(s string) error {
 | |
| 	if j.deferred != "" {
 | |
| 		if _, err := j.out.Write([]byte(j.deferred)); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		j.deferred = ""
 | |
| 	}
 | |
| 	_, err := j.out.Write([]byte(s))
 | |
| 	return err
 | |
| }
 |