Skip to content

Latest commit

 

History

History
139 lines (108 loc) · 4.05 KB

README.md

File metadata and controls

139 lines (108 loc) · 4.05 KB

Documentation Go workflow CircleCI codecov Go Report Card GitHub tag (latest SemVer)

jq

jq is a jq-like library. It's intended to simplify working with unstructured data. It's aimed to be almost fully compatible with original jq. It doesn't use reflection or usafe hacks. It reuses memory -> less allocs -> less gc pressure -> better performance and less memory and cpu usage.

Usage

Data Reshaping

Code is from examples.

	// Suppose some API returns this object where among tons of other useless keys we have this:
	input := `{
		    "data": {
		        "entries": [
		            {"data": {"the actual interesting object": "here"}},
		            {"data": {"the actual interesting object": "here"}},
		            {"data": {"the actual interesting object": "here"}},
		        ],
		        "pagination": {
		            "next_cursor": "some_cursor"
		        }
		    }
		}`

	/*
		And we want:

		{
		    "results": [
		        {"the actual interesting object": "here"},
		        {"the actual interesting object": "here"},
		        {"the actual interesting object": "here"},
		    ],
		    "cursor": "some_cursor"
		}
	*/

	// Prepare filter, decoder, encoder, and buffer.
	// Better to do it once and reuse, but in a single threaded manner.

	// This is equivalent to jq program:
	// {results: [.data.entries[].data], cursor: .data.pagination.next_cursor}
	f := jq.NewObject(
		"results", jq.NewArray(
			jq.NewQuery("data", "entries", jq.NewIter(), "data"),
		),
		"cursor", jq.NewQuery("data", "pagination", "next_cursor"),
	)

	s := jq.NewSandwich(
		jqjson.NewDecoder(),
		jqjson.NewEncoder(),
	)
	s.Reset() // call if s is reused. Process* do it implicitly.

	// read data
	r := strings.NewReader(input) // net.Conn or something

	buf, err := io.ReadAll(r)
	_ = err // if err != nil ...

	var res []byte

	res, err = s.ProcessAll(f, res[:0], buf)
	_ = err // if err != nil ...

	// res now contains what we wanted

Format Conversion

	// Suppose we requested data with the query and got this results in csv format.
	query := "james bond"
	input := `id,graphql_id,name,url,followers
124,ArWqv41,"James Bond","https://page-url.com/profile/124",100500
140,RefqWet,"James Bond Jr.","https://page-url.com/profile/140",1030
`

	// This is equivalent to jq program:
	// {query: "query"} + .
	f := jq.NewPlus(jq.NewObject("query", jq.NewLiteral(query)), jq.Dot{})

	d := jqcsv.NewDecoder()
	d.Tag = cbor.Map
	d.Header = true

	e := jqjson.NewEncoder()
	e.Separator = []byte{'\n'}

	s := jq.NewSandwich(d, e)
	s.Reset() // call if s is reused. Process* do it implicitly.

	buf := []byte(input)
	res, err := s.ProcessAll(f, nil, buf)
	_ = err // if err != nil ...

	_ = res // res is a new line separated list of json objects with `"query": "james bond"` added to each object

Extracting Data From the Hell

	// generated by command
	// jq -nc '{key3: "value"} | {key2: (. | tojson)} | @base64 | {key1: .}'
	data := []byte(`{"key1":"eyJrZXkyIjoie1wia2V5M1wiOlwidmFsdWVcIn0ifQ=="}`)

	f := jq.NewQuery(
		"key1",
		jqbase64.NewDecoder(base64.StdEncoding),
		jqjson.NewDecoder(),
		"key2",
		jqjson.NewDecoder(),
		"key3",
	)

	s := jq.NewSandwich(jqjson.NewDecoder(), nil)
	s.Reset() // call if s is reused. Process* do it implicitly.

	res, _, _, err := s.DecodeApply(f, data, 0)
	_ = err // if err != nil {

	value, err := s.Buffer.Reader().BytesChecked(res)
	_ = err // if err != nil { // not a string (or bytes)

	fmt.Printf("%s\n", value)