Skip to content

Commit 9e12543

Browse files
authored
cm: implement JSON [un]marshaling for List types
Merge pull request #279 from bytecodealliance/ydnar/serialize-list
2 parents db23ebb + 411bd9a commit 9e12543

File tree

5 files changed

+387
-12
lines changed

5 files changed

+387
-12
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
test-go:
4242
name: Test with Go
4343
runs-on: ubuntu-latest
44-
timeout-minutes: 5
44+
timeout-minutes: 15
4545
strategy:
4646
matrix:
4747
go-version: ["1.22", "1.23"]

cm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
77
### Added
88

99
- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`.
10+
- Initial support for JSON serialization of WIT types, starting with `list` and `record`.
1011

1112
## [v0.1.0] — 2024-12-14
1213

cm/list.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package cm
22

3-
import "unsafe"
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"unsafe"
7+
)
48

59
// List represents a Component Model list.
610
// The binary representation of list<T> is similar to a Go slice minus the cap field.
@@ -58,3 +62,57 @@ func (l list[T]) Data() *T {
5862
func (l list[T]) Len() uintptr {
5963
return l.len
6064
}
65+
66+
// MarshalJSON implements json.Marshaler.
67+
func (l list[T]) MarshalJSON() ([]byte, error) {
68+
if l.len == 0 {
69+
return []byte("[]"), nil
70+
}
71+
72+
s := l.Slice()
73+
var zero T
74+
if unsafe.Sizeof(zero) == 1 {
75+
// The default Go json.Encoder will marshal []byte as base64.
76+
// We override that behavior so all int types have the same serialization format.
77+
// []uint8{1,2,3} -> [1,2,3]
78+
// []uint32{1,2,3} -> [1,2,3]
79+
return json.Marshal(sliceOf(s))
80+
}
81+
return json.Marshal(s)
82+
}
83+
84+
type slice[T any] []entry[T]
85+
86+
func sliceOf[S ~[]E, E any](s S) slice[E] {
87+
return *(*slice[E])(unsafe.Pointer(&s))
88+
}
89+
90+
type entry[T any] [1]T
91+
92+
func (v entry[T]) MarshalJSON() ([]byte, error) {
93+
return json.Marshal(v[0])
94+
}
95+
96+
// UnmarshalJSON implements json.Unmarshaler.
97+
func (l *list[T]) UnmarshalJSON(data []byte) error {
98+
if bytes.Equal(data, nullLiteral) {
99+
return nil
100+
}
101+
102+
var s []T
103+
err := json.Unmarshal(data, &s)
104+
if err != nil {
105+
return err
106+
}
107+
108+
l.data = unsafe.SliceData([]T(s))
109+
l.len = uintptr(len(s))
110+
111+
return nil
112+
}
113+
114+
// nullLiteral is the JSON representation of a null literal.
115+
// By convention, to approximate the behavior of Unmarshal itself,
116+
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
117+
// See https://pkg.go.dev/encoding/json#Unmarshaler for more information.
118+
var nullLiteral = []byte("null")

0 commit comments

Comments
 (0)