-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarshal.go
138 lines (119 loc) · 3.4 KB
/
marshal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package json5
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// Marshal converts an interface{} into a JSON5 string.
func Marshal(value interface{}) (string, error) {
return marshalValue(value, "", 0)
}
// MarshalIndent converts an interface{} into a JSON5 string with indentation.
func MarshalIndent(value interface{}, indent string) (string, error) {
return marshalValue(value, indent, 0)
}
// marshalValue recursively converts a Go value into a JSON5 string
func marshalValue(value interface{}, indent string, depth int) (string, error) {
switch v := value.(type) {
case nil:
return "null", nil
case bool:
if v {
return "true", nil
}
return "false", nil
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
return fmt.Sprintf("%v", v), nil
case string:
return marshalString(v), nil
case []interface{}:
return marshalArray(v, indent, depth)
case map[string]interface{}:
return marshalObject(v, indent, depth)
default:
// Handle other types if needed (custom types, etc.)
return "", fmt.Errorf("unsupported type: %v", reflect.TypeOf(value))
}
}
// marshalString handles string values and escapes necessary characters
func marshalString(s string) string {
// Escape necessary characters in the string
replacer := strings.NewReplacer(
"\\", "\\\\",
"\"", "\\\"",
"\n", "\\n",
"\r", "\\r",
"\t", "\\t",
)
escaped := replacer.Replace(s)
return fmt.Sprintf("\"%s\"", escaped)
}
// marshalArray handles slices of interface{} and converts them into JSON5 arrays
func marshalArray(array []interface{}, indent string, depth int) (string, error) {
var sb strings.Builder
sb.WriteString("[")
newIndent := strings.Repeat(indent, depth+1)
first := true
for _, item := range array {
if !first {
sb.WriteString(", ")
}
first = false
itemStr, err := marshalValue(item, indent, depth+1)
if err != nil {
return "", err
}
sb.WriteString("\n")
sb.WriteString(newIndent)
sb.WriteString(itemStr)
}
sb.WriteString("\n")
sb.WriteString(strings.Repeat(indent, depth))
sb.WriteString("]")
return sb.String(), nil
}
// marshalObject handles maps and converts them into JSON5 objects
func marshalObject(obj map[string]interface{}, indent string, depth int) (string, error) {
var sb strings.Builder
sb.WriteString("{")
newIndent := strings.Repeat(indent, depth+1)
for key, value := range obj {
keyStr := marshalKey(key)
valueStr, err := marshalValue(value, indent, depth+1)
if err != nil {
return "", err
}
sb.WriteString("\n")
sb.WriteString(newIndent)
sb.WriteString(keyStr)
sb.WriteString(": ")
sb.WriteString(valueStr)
sb.WriteString(",")
}
sb.WriteString("\n")
sb.WriteString(strings.Repeat(indent, depth))
sb.WriteString("}")
return sb.String(), nil
}
// marshalKey checks if a key can be unquoted in JSON5 (simple identifier) or must be quoted
func marshalKey(key string) string {
// Check if the key can be unquoted (simple identifier rules for JSON5)
if isSimpleIdentifier(key) {
return key
}
// Otherwise, quote the key
return strconv.Quote(key)
}
// isSimpleIdentifier checks if a string qualifies as a simple identifier (unquoted in JSON5)
func isSimpleIdentifier(key string) bool {
if len(key) == 0 {
return false
}
for i, ch := range key {
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '$' || (i > 0 && ch >= '0' && ch <= '9')) {
return false
}
}
return true
}