jsonparser icon indicating copy to clipboard operation
jsonparser copied to clipboard

Iterator support for Arrays & Objects

Open 80avin opened this issue 3 years ago • 1 comments

I am really impressed by this project but I find the current API less helpful.

Hence, proposing a new way to iterate over the array elements or object entries.

Example:

data := []bytes(`{"menu": {
    "header": "SVG Viewer",
    "items": [
        {"id": "Open"},
        {"id": "OpenNew", "label": "Open New"},
        null,
        {"id": "ZoomIn", "label": "Zoom In"},
        {"id": "ZoomOut", "label": "Zoom Out"},
        {"id": "OriginalView", "label": "Original View"},
        null,
        {"id": "Quality"},
        {"id": "Pause"},
        {"id": "Mute"},
        null,
        {"id": "Find", "label": "Find..."},
        {"id": "FindAgain", "label": "Find Again"},
        {"id": "Copy"},
        {"id": "CopyAgain", "label": "Copy Again"},
        {"id": "CopySVG", "label": "Copy SVG"},
        {"id": "ViewSVG", "label": "View SVG"},
        {"id": "ViewSource", "label": "View Source"},
        {"id": "SaveAs", "label": "Save As"},
        null,
        {"id": "Help"},
        {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
}}`)

// Either support object notation
iterator, err := jsonparser.ArrayIterator(data, "menu", "items")
for val, err = iterator.next(); err == nil && iterator.hasNext(); val, err = iterator.next() {
    // iterator.hasNext() can be replaced by err = errors.New("Done")
    // do anything or break as required.
  }

// OR support closure notation
next(), hasNext, err = jsonparser.ArrayIterator(data, "menu", "items")
for val, hasNext, err = next(); err == nil && hasNext; val, hasNext, err = next() {
  // hasNext can be dropped as err = errors.New("Done") can convey same information.
  // do anything or break as required.
}

Similar syntax can be used for iterating object entries (key/values), or other functions like EachKey which ask for a callback to iterate over values.

This will prevent the callback hell and also make the code more efficient & readable since we need not iterate over all entries to find one.

PS. I can also contribute and create a PR once it is approved.

80avin avatar Oct 02 '22 09:10 80avin

In go 1.23 using the new iter package this can be done quite easily without needing to extend the library.

play here

range over ids:

func main() {
	for id := range Response(data).IterateIDs() {
		fmt.Println(id)
	}
}

define iterator:

// Response for a menu.
type Response []byte

// IterateIDs over IDs.
func (r Response) IterateIDs() iter.Seq[string] {
	return func(yield func(string) bool) {
		jsonparser.ArrayEach(r, func(value []byte, _ jsonparser.ValueType, _ int, _ error) {
			nextID, err := jsonparser.GetUnsafeString(value, "id")
			if err != nil || !yield(nextID) {
				return
			}
		}, "menu", "items")
	}
}

rossb83 avatar Sep 08 '24 07:09 rossb83