Skip to content

Commit e4dfd31

Browse files
committed
- [#] updated cmd:fmt -s, --concise option implemented
1 parent 8e6155b commit e4dfd31

File tree

8 files changed

+226
-1
lines changed

8 files changed

+226
-1
lines changed

imp_fmt.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ func (x *FmtCommand) Exec(args []string) error {
2828

2929
var out bytes.Buffer
3030
var err error
31-
if Opts.Compact {
31+
if x.Concise {
32+
var formatted string
33+
formatted, err = FormatJSONMixed(data)
34+
out.WriteString(formatted)
35+
} else if Opts.Compact {
3236
err = json.Compact(&out, data)
3337
} else {
3438
err = json.Indent(&out, data, Opts.Prefix, Opts.Indent)

jsonfiddle_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ func TestExec(t *testing.T) {
6868
testIt(t, "Schedules", "fmt", "-i", "Schedules.json")
6969
testIt(t, "CustomerP", "fmt", "-p", "-i", "CustomerP.json")
7070
testIt(t, "CustomerPC", "fmt", "-c", "-p", "-i", "CustomerP.json")
71+
testIt(t, "FmtConciseO", "fmt", "-s", "-i", "FmtConciseO.json")
72+
testIt(t, "FmtConciseA", "fmt", "--concise", "-i", "FmtConciseA.json")
7173
// -- sort
7274
t.Logf("\n\n== Testing Basic sort Functions\n\n")
7375
testIt(t, "CustomerSI", "sort", "-i", "Customer.json")

prop_fmt.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// FormatJSONMixed handles two top-level JSON structures:
10+
// 1. A single-key object containing an array: {"key": [elements]}
11+
// 2. A pure array of objects: [elements]
12+
// It formats the output with compact, single-line array elements.
13+
func FormatJSONMixed(input interface{}) (string, error) {
14+
var rawJSON []byte
15+
var err error
16+
17+
// 1. Ensure we have raw JSON bytes to work with.
18+
switch v := input.(type) {
19+
case []byte:
20+
rawJSON = v
21+
case string:
22+
rawJSON = []byte(v)
23+
default:
24+
rawJSON, err = json.Marshal(v)
25+
if err != nil {
26+
return "", fmt.Errorf("failed to marshal input: %v", err)
27+
}
28+
}
29+
30+
// 2. Unmarshal into a generic interface{} to check its type.
31+
var genericData interface{}
32+
if err := json.Unmarshal(rawJSON, &genericData); err != nil {
33+
return "", fmt.Errorf("failed to unmarshal JSON: %v", err)
34+
}
35+
36+
var sb strings.Builder
37+
38+
// Determine the array source based on the top-level structure
39+
switch v := genericData.(type) {
40+
case map[string]interface{}:
41+
// --- Case 1: Root is a single-key object ---
42+
43+
// Validation check ensures only one key exists
44+
if len(v) != 1 {
45+
return "", fmt.Errorf("input JSON object must have exactly one root key, found %d", len(v))
46+
}
47+
48+
sb.WriteString("{ ")
49+
50+
// Use a composite loop/assignment to get the single key/value pair
51+
// because Go maps are unordered and cannot be accessed by index or
52+
// a known key without knowing the key name first
53+
var key string
54+
var value interface{}
55+
for k, val := range v {
56+
key = k
57+
value = val
58+
break // geet the 1st (and only) pair, see earlier strong validation check
59+
}
60+
61+
// Validate the value is an array (slice)
62+
elementArray, ok := value.([]interface{})
63+
if !ok {
64+
return "", fmt.Errorf("value for key %q is not a JSON array", key)
65+
}
66+
67+
keyBytes, _ := json.Marshal(key)
68+
// Write the indented key and array start
69+
//sb.WriteString(" ") // 2-space indent
70+
sb.Write(keyBytes)
71+
sb.WriteString(": [\n")
72+
73+
// Format the elements
74+
if err := formatArrayElements(&sb, elementArray, 4); err != nil {
75+
return "", err
76+
}
77+
78+
// Write the closing array and object
79+
sb.WriteString(" ]\n")
80+
sb.WriteString("}")
81+
82+
case []interface{}:
83+
// --- Case 2: Root is a pure array ---
84+
85+
sb.WriteString("[\n")
86+
87+
// Format the elements
88+
if err := formatArrayElements(&sb, v, 2); err != nil {
89+
return "", err
90+
}
91+
92+
// Write the closing array bracket
93+
sb.WriteString("]")
94+
95+
default:
96+
return "", fmt.Errorf("unsupported root JSON structure. Must be an array object or an array")
97+
}
98+
99+
return sb.String(), nil
100+
}
101+
102+
// formatArrayElements contains the core logic for marshalling and indenting elements.
103+
func formatArrayElements(sb *strings.Builder, elements []interface{}, indentSpaces int) error {
104+
indent := strings.Repeat(" ", indentSpaces)
105+
numElements := len(elements)
106+
107+
for i, element := range elements {
108+
// Marshal the single element (compact, one-line string)
109+
elemBytes, err := json.Marshal(element)
110+
if err != nil {
111+
return fmt.Errorf("failed to marshal element %d: %v", i, err)
112+
}
113+
114+
// Write the indented element
115+
sb.WriteString(indent)
116+
sb.Write(elemBytes)
117+
118+
// Add comma and newline
119+
if i < numElements-1 {
120+
sb.WriteString(",")
121+
}
122+
sb.WriteString("\n")
123+
}
124+
return nil
125+
}

prop_fmt_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// NOTE: The FormatJSONMixed function and the Element/Root structs
8+
// are assumed to be in a file named 'json_formatter.go' in the same directory.
9+
10+
// Test data used for the Object-Wrapped Array case
11+
type testObjectData struct {
12+
Users []struct {
13+
ID int `json:"id"`
14+
Active bool `json:"active"`
15+
} `json:"users"`
16+
}
17+
18+
// Test data used for the Pure Array case
19+
type testArrayData []struct {
20+
Name string `json:"name"`
21+
City string `json:"city"`
22+
}
23+
24+
func TestFormatJSONMixed_Object(t *testing.T) {
25+
// GIVEN a structured Go object that will serialize to {"key": [elements]}
26+
testData := testObjectData{
27+
Users: []struct {
28+
ID int `json:"id"`
29+
Active bool `json:"active"`
30+
}{
31+
{ID: 1, Active: true},
32+
{ID: 2, Active: false},
33+
},
34+
}
35+
36+
// The expected output for this structure
37+
expected := `{ "users": [
38+
{"active":true,"id":1},
39+
{"active":false,"id":2}
40+
]
41+
}`
42+
43+
// WHEN the formatting function is called
44+
formatted, err := FormatJSONMixed(testData)
45+
46+
// THEN it should format correctly without error
47+
if err != nil {
48+
t.Fatalf("FormatJSONMixed failed with error: %v", err)
49+
}
50+
51+
if formatted != expected {
52+
t.Errorf("Object Test Failed.\nExpected:\n%s\nGot:\n%s", expected, formatted)
53+
}
54+
}
55+
56+
func TestFormatJSONMixed_Array(t *testing.T) {
57+
// GIVEN a structured Go array that will serialize to [elements]
58+
testData := testArrayData{
59+
{Name: "Alice", City: "NYC"},
60+
{Name: "Bob", City: "LA"},
61+
{Name: "Charlie", City: "SF"},
62+
}
63+
64+
// The expected output for this structure
65+
expected := `[
66+
{"city":"NYC","name":"Alice"},
67+
{"city":"LA","name":"Bob"},
68+
{"city":"SF","name":"Charlie"}
69+
]`
70+
71+
// WHEN the formatting function is called
72+
formatted, err := FormatJSONMixed(testData)
73+
74+
// THEN it should format correctly without error
75+
if err != nil {
76+
t.Fatalf("FormatJSONMixed failed with error: %v", err)
77+
}
78+
79+
if formatted != expected {
80+
t.Errorf("Array Test Failed.\nExpected:\n%s\nGot:\n%s", expected, formatted)
81+
}
82+
}

test/FmtConciseA.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"city":"NYC","name":"Alice"},{"city":"LA","name":"Bob"},{"city":"SF","name":"Charlie"}]

test/FmtConciseA.ref

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
{"city":"NYC","name":"Alice"},
3+
{"city":"LA","name":"Bob"},
4+
{"city":"SF","name":"Charlie"}
5+
]

test/FmtConciseO.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"users":[{"active":true,"id":1},{"active":false,"id":2}]}

test/FmtConciseO.ref

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{ "users": [
2+
{"active":true,"id":1},
3+
{"active":false,"id":2}
4+
]
5+
}

0 commit comments

Comments
 (0)