Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ jobs:
- name: Test
env:
GOFLAGS: ${{ matrix.flags }}
run: go test -coverprofile=profile.out -covermode=atomic -v -coverpkg=./... ./...
run: go test -coverprofile=profile-go${{ matrix.go-version }}${{ matrix.flags }}.out -covermode=atomic -v -coverpkg=./... ./...

- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverage
name: coverage-go${{ matrix.go-version }}${{ matrix.flags }}
path: |
profile.out
profile-go${{ matrix.go-version }}${{ matrix.flags }}.out
if-no-files-found: error
retention-days: 1

Expand All @@ -52,9 +52,10 @@ jobs:
uses: actions/checkout@v4

- name: Download artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: coverage
pattern: coverage-*
merge-multiple: true

- name: Send coverage
uses: codecov/codecov-action@v3
Expand Down
73 changes: 70 additions & 3 deletions insane.go
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,65 @@ func (n *Node) MutateToStrict() *StrictNode {
return &StrictNode{n}
}

// ConvertToRoot **experimental** **not safe** function. It converts the node to `Root` and
// removes it from its parent tree. The root parameter must the root of the current node,
// so the converted root node utilizes the same decoder and nodePool. The node's parent is
// becoming `nil` so it behaves like normal root node (e.g. on `(*Node).Suicide`).
//
// This function can be used when it is needed to use JSON subtree as a whole tree with
// the root on the subtree top node. For example when treating array elements as separate
// JSON trees.
//
// This function is unsafe because it shares the same decoder as its parent tree while the
// decoder itself points to the parent root and that can lead to some unexpected behaviour.
func (n *Node) ConvertToRoot(root *Root) *Root {
// remove node from its parent tree
n.Suicide()
n.parent = nil
return &Root{
n,
root.decoder,
}
}

// CopyFromNode copies all data from the src node to the current node.
// `root` must be a root of the current node to utilize the same node pool.
// If the `src` node is a node tree, the whole tree will be copied recursively.
// This function is designed to use for copying data from node of one JSON tree
// to another so they don't mutate each others data and utilize their own node
// pools so the garbage collector can clean released nodes correctly.
func (n *Node) CopyFromNode(root *Root, src *Node) *Node {
if n == nil || src == nil {
return nil
}

n.bits = src.bits
n.data = strings.Clone(src.data)
n.next = n.getNode(root)
n.next.parent = n.parent
n.next.CopyFromNode(root, src.next)

if len(src.nodes) > 0 {
n.nodes = make([]*Node, 0, len(src.nodes))
for _, child := range src.nodes {
newChild := n.getNode(root)
newChild.parent = n
n.nodes = append(n.nodes, newChild.CopyFromNode(root, child))
}
}

if src.fields != nil {
srcFields := *src.fields
newFields := make(map[string]int, len(srcFields))
for k, v := range srcFields {
newFields[k] = v
}
n.fields = &newFields
}

return n
}

func (n *Node) DigField(path ...string) *Node {
if n == nil || len(path) == 0 {
return nil
Expand Down Expand Up @@ -1806,17 +1865,25 @@ func (n *Node) getIndex() int {
// ******************** //

func (d *decoder) initPool() {
d.nodePool = make([]*Node, StartNodePoolSize, StartNodePoolSize)
s := make([]Node, StartNodePoolSize)
d.nodePool = make([]*Node, StartNodePoolSize)
for i := 0; i < StartNodePoolSize; i++ {
d.nodePool[i] = &Node{}
d.nodePool[i] = &s[i]
}
}

func (d *decoder) expandPool() []*Node {
c := cap(d.nodePool)
newSlice := make([]*Node, 0, c+c)
newSlice = append(newSlice, d.nodePool...)
s := make([]Node, c)
for i := 0; i < c; i++ {
d.nodePool = append(d.nodePool, &Node{})
newSlice = append(newSlice, &s[i])
}
for i := range d.nodePool {
d.nodePool[i] = nil
}
d.nodePool = newSlice

return d.nodePool
}
Expand Down
49 changes: 47 additions & 2 deletions insane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ func TestDecodeErr(t *testing.T) {
{json: `falsenull`, err: ErrUnexpectedJSONEnding},
{json: `null:`, err: ErrUnexpectedJSONEnding},


// ok
{json: `0`, err: nil},
{json: `1.0`, err: nil},
Expand Down Expand Up @@ -374,7 +373,7 @@ func TestAddElement(t *testing.T) {
for index := 0; index < test.count; index++ {
root.AddElement()
l := len(root.AsArray())
assert.True(t, root.Dig(strconv.Itoa(l - 1)).IsNull(), "wrong node type")
assert.True(t, root.Dig(strconv.Itoa(l-1)).IsNull(), "wrong node type")
}
assert.Equal(t, test.result, root.EncodeToString(), "wrong encoding")
Release(root)
Expand Down Expand Up @@ -1054,3 +1053,49 @@ func TestIndex(t *testing.T) {

assert.Equal(t, index, node.getIndex(), "wrong index")
}

func TestCopyFromNode(t *testing.T) {
json := `{"first":["s1","s2","s3"],"second":[{"s4":true},{"s5":false}]}`
root1, err := DecodeString(json)
assert.NoError(t, err, "error while decoding")
assert.NotNil(t, root1, "node shouldn't be nil")

root2 := Spawn()
clonedRoot := root2.CopyFromNode(root2, root1.Node)
assert.NotNil(t, clonedRoot, "node shouldn't be nil")

array1 := root1.Dig("first").AsArray()
array2 := root2.Dig("first").AsArray()
assert.Equal(t, len(array1), len(array2))
for i := range array1 {
assert.Equal(t, array1[i].AsString(), array2[i].AsString())
}

origVal := strings.Clone(array1[0].data)
array2[0].data = "newData"
assert.NotEqual(t, array1[0].AsString(), array2[0].AsString(), "values must be different, first value must be %q, but it was changed to %q", origVal, array1[0].data)
}

func TestConvertToRoot(t *testing.T) {
json := `[{"a":"1"},{"b":"2"},{"c":"3"}]`
root1, err := DecodeString(json)
assert.NoError(t, err, "error while decoding")
assert.NotNil(t, root1, "root shouldn't be nil")
assert.True(t, len(root1.AsArray()) == 3, "root must be an array with 3 elements")

node2 := root1.Dig("2")
assert.NotNil(t, node2, "node shouldn't be nil")
assert.True(t, node2.IsObject(), "node must be an object")

root1FieldVal := node2.Dig("c")
assert.NotNil(t, root1FieldVal, "node must not be nil")

root2 := node2.ConvertToRoot(root1)
assert.NotNil(t, root2, "root shouldn't be nil")
// after ConvertToRoot the node is deleted from its parent tree
assert.Nil(t, root1.Dig("2"), "node must be nil")

root2FieldVal := root2.Dig("c")
assert.NotNil(t, root2FieldVal, "node must not be nil")
assert.Equal(t, root1FieldVal.AsString(), root2FieldVal.AsString(), "values must be equal")
}