I had a little play and came up with this. But it may not be the best way, and I'd be interested to see what your attempts were like, because after all if we both came at a solution I'm sure it'd be twice as good!
But I would start from something like:
true as $doHeaders
| . / "\n"
| map(. / ", ")
| (if $doHeaders then .[0] else [range(0; (.[0] | length)) | tostring] end) as $headers
| .[if $doHeaders then 1 else 0 end:][]
| . as $values
| keys
| map({($headers[.]): $values[.]})
Working Example
The variable $doHeaders
controls whether to read the top line as a header line. In your case you want it as true, but I added it for future SO users and because, well, I had an excellent breakfast today and the weather is lovely, so why not?
Little explanation:
1) . / "\n"
Split by line...
2) map(. / ", ")
... and comma (Big gotcha: In your version, you'll want to use a regex based split because like this you'll split on commas inside quotation marks too. I just used this because it's terse, and that makes my solution look cool right?)
3) if $doHeaders then...
Here we create an array of strings keys or numbers depending on the number of elements in the first row and whether the first row is a header row
4) .[if $doHeaders then 1 else 0 end:]
Ok, so trim off the top line if it's a header
5) map({($headers[.]): $values[.]})
Above we go over each row in the former csv, and put the $values
into a variable and the keys into a pipe. Then we construct your desired object.
Of course you'll want to use a few regexes to fill in the gotchas, but I hope that starts you on the way.