Add CLI JSON value parsing
This commit is contained in:
76
cli_json.go
Normal file
76
cli_json.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseCLIJSON parses exactly one JSON value for use in CLI inputs. Integer
|
||||
// tokens become int64, or uint64 for larger nonnegative integers; decimal and
|
||||
// exponent tokens become float64. Nested values follow the same rules. Syntax
|
||||
// errors and numbers outside the selected Go types' ranges are returned.
|
||||
func ParseCLIJSON(input string) (any, error) {
|
||||
decoder := json.NewDecoder(strings.NewReader(input))
|
||||
decoder.UseNumber()
|
||||
var value any
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var extra any
|
||||
if err := decoder.Decode(&extra); err != io.EOF {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected additional JSON value")
|
||||
}
|
||||
return convertCLIJSON(value)
|
||||
}
|
||||
|
||||
// CLI inputs use ordinary Go numbers for upstream consumers. Classification
|
||||
// follows the token's syntax, so 1.0 and 1e0 remain float64. Unlike report
|
||||
// serialization, this boundary deliberately accepts float64 approximation.
|
||||
func convertCLIJSON(value any) (any, error) {
|
||||
switch v := value.(type) {
|
||||
case json.Number:
|
||||
token := string(v)
|
||||
if strings.ContainsAny(token, ".eE") {
|
||||
result, err := strconv.ParseFloat(token, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON number %q: %w", token, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
result, err := strconv.ParseInt(token, 10, 64)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !strings.HasPrefix(token, "-") {
|
||||
unsigned, unsignedErr := strconv.ParseUint(token, 10, 64)
|
||||
if unsignedErr == nil {
|
||||
return unsigned, nil
|
||||
}
|
||||
return nil, fmt.Errorf("JSON integer %q: %w", token, unsignedErr)
|
||||
}
|
||||
return nil, fmt.Errorf("JSON integer %q: %w", token, err)
|
||||
case []any:
|
||||
for index, item := range v {
|
||||
converted, err := convertCLIJSON(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[index] = converted
|
||||
}
|
||||
case map[string]any:
|
||||
for key, item := range v {
|
||||
converted, err := convertCLIJSON(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[key] = converted
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
Reference in New Issue
Block a user