Skip to content

Commit 4b8e379

Browse files
support packing []interface{} i.e any tuple, or homogenous array
1 parent 391d4be commit 4b8e379

File tree

4 files changed

+261
-2
lines changed

4 files changed

+261
-2
lines changed

.vscode/launch.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": []
7+
}

access/put.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,70 @@ func (p *PutAccess) AddMap(m map[string][]byte) {
294294

295295
}
296296

297+
// AddStringArray packs a []string as a tuple
298+
func (p *PutAccess) AddStringArray(arr []string) {
299+
// use tuple for local array for now
300+
p.offsets = binary.LittleEndian.AppendUint16(
301+
p.offsets,
302+
types.EncodeHeader(p.position, types.TypeTuple),
303+
)
304+
305+
if len(arr) == 0 {
306+
return
307+
}
308+
309+
nested := NewPutAccessFromPool()
310+
for _, s := range arr {
311+
nested.AddString(s)
312+
}
313+
p.appendAndReleaseNested(nested)
314+
}
315+
316+
func (p *PutAccess) AddAnyTuple(m []interface{}) {
317+
// encode tuple header
318+
p.offsets = binary.LittleEndian.AppendUint16(
319+
p.offsets,
320+
types.EncodeHeader(p.position, types.TypeTuple),
321+
)
322+
323+
if len(m) == 0 {
324+
return
325+
}
326+
327+
nested := NewPutAccessFromPool()
328+
for _, elem := range m {
329+
packAnyValue(nested, elem)
330+
}
331+
p.appendAndReleaseNested(nested)
332+
}
333+
334+
func (p *PutAccess) AddAnyTupleSortedMap(m []interface{}) {
335+
// encode tuple header
336+
p.offsets = binary.LittleEndian.AppendUint16(
337+
p.offsets,
338+
types.EncodeHeader(p.position, types.TypeTuple),
339+
)
340+
341+
if len(m) == 0 {
342+
return
343+
}
344+
345+
nested := NewPutAccessFromPool()
346+
for _, elem := range m {
347+
packAnyValueSortedMap(nested, elem)
348+
}
349+
p.appendAndReleaseNested(nested)
350+
}
351+
352+
func (p *PutAccess) AddNull(m []interface{}) {
353+
// encode tuple header
354+
p.offsets = binary.LittleEndian.AppendUint16(
355+
p.offsets,
356+
types.EncodeHeader(p.position, types.TypeNull),
357+
)
358+
359+
}
360+
297361
func (p *PutAccess) AddMapStr(m map[string]string) {
298362

299363
p.offsets = binary.LittleEndian.AppendUint16(p.offsets, types.EncodeHeader(p.position, types.TypeMap))
@@ -340,6 +404,8 @@ func (p *PutAccess) AddMapSortedKey(m map[string][]byte) {
340404

341405
func packAnyValue(p *PutAccess, v any) {
342406
switch val := v.(type) {
407+
case nil:
408+
p.AddNull(nil)
343409
case string:
344410
p.AddString(val)
345411
case []byte:
@@ -372,6 +438,10 @@ func packAnyValue(p *PutAccess, v any) {
372438
p.AddMapAny(val)
373439
case map[string][]byte:
374440
p.AddMap(val)
441+
case []string:
442+
p.AddStringArray(val)
443+
case []interface{}:
444+
p.AddAnyTuple(val)
375445
case Packable:
376446
val.PackInto(p)
377447
default:
@@ -380,8 +450,10 @@ func packAnyValue(p *PutAccess, v any) {
380450
}
381451
}
382452

383-
func packAnyValueSorted(p *PutAccess, v any) {
453+
func packAnyValueSortedMap(p *PutAccess, v any) {
384454
switch val := v.(type) {
455+
case nil:
456+
p.AddNull(nil)
385457
case string:
386458
p.AddString(val)
387459
case []byte:
@@ -408,6 +480,8 @@ func packAnyValueSorted(p *PutAccess, v any) {
408480
p.AddMapSortedKey(val)
409481
case Packable:
410482
val.PackInto(p)
483+
case []interface{}:
484+
p.AddAnyTupleSortedMap(val)
411485
default:
412486
// Optional: panic or skip unsupported types
413487
panic(fmt.Sprintf("packAnyValue: unsupported type %T", val))
@@ -436,7 +510,7 @@ func (p *PutAccess) AddMapAnySortedKey(m map[string]any) {
436510
nested := NewPutAccessFromPool()
437511
for _, k := range keys {
438512
nested.AddString(k)
439-
packAnyValueSorted(nested, m[k])
513+
packAnyValueSortedMap(nested, m[k])
440514
}
441515
p.appendAndReleaseNested(nested)
442516
}

types/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const (
1111
TypeExtendedTagContainer Type = 2
1212
TypeFloating Type = 3
1313
TypeTuple Type = 4
14+
TypeNull Type = 4
1415
TypeBool Type = 5
1516
TypeString Type = 6 // used for both string and []byte small chunks
1617
TypeByteArray Type = 6

usage/usage_test.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package usage
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"testing"
8+
9+
"github.com/quickwritereader/PackOS/access"
10+
)
11+
12+
const testJson = `
13+
{
14+
"meta": {
15+
"version": "1.0.0",
16+
"author": "Copilot",
17+
"timestamp": "2025-12-15T11:21:00Z",
18+
"description": "Large JSON for testing decode and pack length comparison"
19+
},
20+
"users": [
21+
{
22+
"id": 1,
23+
"name": "Alice",
24+
"roles": ["admin", "editor", "viewer"],
25+
"settings": {
26+
"theme": "dark",
27+
"notifications": true,
28+
"languages": ["en", "fr", "de", "es"]
29+
},
30+
"activity": [
31+
{"date": "2025-01-01", "action": "login", "ip": "192.168.0.1"},
32+
{"date": "2025-01-02", "action": "upload", "file": "report.pdf"},
33+
{"date": "2025-01-03", "action": "logout"}
34+
]
35+
},
36+
{
37+
"id": 2,
38+
"name": "Bob",
39+
"roles": ["viewer"],
40+
"settings": {
41+
"theme": "light",
42+
"notifications": false,
43+
"languages": ["en", "ru"]
44+
},
45+
"activity": [
46+
{"date": "2025-02-10", "action": "login", "ip": "10.0.0.2"},
47+
{"date": "2025-02-11", "action": "download", "file": "data.csv"}
48+
]
49+
}
50+
],
51+
"projects": [
52+
{
53+
"projectId": "P100",
54+
"title": "AI Research",
55+
"status": "active",
56+
"members": [1, 2],
57+
"tasks": [
58+
{"taskId": "T1", "title": "Data Collection", "completed": false},
59+
{"taskId": "T2", "title": "Model Training", "completed": true},
60+
{"taskId": "T3", "title": "Evaluation", "completed": false}
61+
]
62+
},
63+
{
64+
"projectId": "P200",
65+
"title": "Web Development",
66+
"status": "archived",
67+
"members": [2],
68+
"tasks": [
69+
{"taskId": "T10", "title": "Frontend Design", "completed": true},
70+
{"taskId": "T11", "title": "Backend API", "completed": true},
71+
{"taskId": "T12", "title": "Deployment", "completed": true}
72+
]
73+
}
74+
],
75+
"logs": {
76+
"system": [
77+
{"level": "info", "message": "System started", "time": "2025-01-01T00:00:00Z"},
78+
{"level": "warn", "message": "High memory usage", "time": "2025-01-05T12:00:00Z"},
79+
{"level": "error", "message": "Disk failure", "time": "2025-01-10T18:30:00Z"}
80+
],
81+
"application": [
82+
{"level": "debug", "message": "User clicked button", "time": "2025-02-01T09:15:00Z"},
83+
{"level": "info", "message": "File uploaded", "time": "2025-02-02T10:00:00Z"}
84+
]
85+
},
86+
"data": {
87+
"matrix": [
88+
[1,2,3,4,5],
89+
[6,7,8,9,10],
90+
[11,12,13,14,15],
91+
[16,17,18,19,20]
92+
],
93+
"nested": {
94+
"alpha": {
95+
"beta": {
96+
"gamma": {
97+
"delta": "deep value",
98+
"epsilon": [true, false, null, "string", 12345]
99+
}
100+
}
101+
}
102+
},
103+
"largeArray": [
104+
{"index": 0, "value": "A"},
105+
{"index": 1, "value": "B"},
106+
{"index": 2, "value": "C"},
107+
{"index": 3, "value": "D"},
108+
{"index": 4, "value": "E"},
109+
{"index": 5, "value": "F"},
110+
{"index": 6, "value": "G"},
111+
{"index": 7, "value": "H"},
112+
{"index": 8, "value": "I"},
113+
{"index": 9, "value": "J"},
114+
{"index": 10, "value": "K"},
115+
{"index": 11, "value": "L"},
116+
{"index": 12, "value": "M"},
117+
{"index": 13, "value": "N"},
118+
{"index": 14, "value": "O"},
119+
{"index": 15, "value": "P"},
120+
{"index": 16, "value": "Q"},
121+
{"index": 17, "value": "R"},
122+
{"index": 18, "value": "S"},
123+
{"index": 19, "value": "T"},
124+
{"index": 20, "value": "U"},
125+
{"index": 21, "value": "V"},
126+
{"index": 22, "value": "W"},
127+
{"index": 23, "value": "X"},
128+
{"index": 24, "value": "Y"},
129+
{"index": 25, "value": "Z"}
130+
]
131+
}
132+
}
133+
`
134+
135+
// DecodeToGenericMap unmarshals a JSON blob into map[string]interface{}.
136+
// Returns a fully generic structure (maps, slices, primitives).
137+
func DecodeToGenericMap(data []byte) (map[string]interface{}, error) {
138+
var root interface{}
139+
if err := json.Unmarshal(data, &root); err != nil {
140+
return nil, fmt.Errorf("json unmarshal: %w", err)
141+
}
142+
143+
// Ensure the root is an object
144+
obj, ok := root.(map[string]interface{})
145+
if !ok {
146+
return nil, fmt.Errorf("expected JSON object at root, got %T", root)
147+
}
148+
149+
return obj, nil
150+
}
151+
152+
// Safe wrapper: initialize JsonObject once, handle error internally
153+
var JsonObject = func() map[string]interface{} {
154+
obj, err := DecodeToGenericMap([]byte(testJson))
155+
if err != nil {
156+
// You can choose how to handle errors here:
157+
// 1. panic (fail fast)
158+
// 2. return empty map (safe fallback)
159+
// 3. log and return empty map
160+
fmt.Println("failed to decode testJson:", err)
161+
return map[string]interface{}{}
162+
}
163+
return obj
164+
}()
165+
166+
func TestUsage1(t *testing.T) {
167+
fmt.Fprintln(os.Stdout,
168+
"Checking whether Packable can compact a map containing []interface{} values, "+
169+
"even though it was originally designed for strongly typed data.")
170+
171+
fmt.Println(JsonObject)
172+
put := access.NewPutAccess()
173+
put.AddMapAny(JsonObject)
174+
res := put.Pack()
175+
fmt.Fprint(os.Stdout, "Json size: ", len(testJson), "\nPackable byte size:", len(res))
176+
177+
}

0 commit comments

Comments
 (0)