如果您有这样的csv数据集:
name, age, gender
john, 20, male
jane, 30, female
bob, 25, male
你能说到这个:
[ {"name": "john", "age": 20, "gender: "male"},
{"name": "jane", "age": 30, "gender: "female"},
{"name": "bob", "age": 25, "gender: "male"} ]
仅使用jq?
我发现了this文章,其中显示了我尝试做的事情,但它使用的是“手册”#39;标头字段到值的映射。我不需要/想要重命名标题字段并且有很多字段。我也不想每次布局更改时都要更改脚本/命令。
是否可以动态提取标题,然后使用jq one-liner将它们与值组合在一起?
答案 0 :(得分:18)
总之 - 是的!
jq通常非常适合文本争用,对于具有正则表达式支持的版本尤其如此。例如,在正则表达式支持下,给定问题陈述所需的修整是微不足道的。
由于jq 1.5rc1包含正则表达式支持并且自2015年1月1日起可用,因此以下程序假定版本为jq 1.5;如果你想让它与jq 1.4一起使用,那么请看两个"对于jq 1.4"评论。
另请注意,此程序无法处理CSV的一般性和复杂性。 (对于更常处理CSV的类似方法,请参阅https://github.com/stedolan/jq/wiki/Cookbook#convert-a-csv-file-with-headers-to-json)
# objectify/1 takes an array of string values as inputs, converts
# numeric values to numbers, and packages the results into an object
# with keys specified by the "headers" array
def objectify(headers):
# For jq 1.4, replace the following line by: def tonumberq: .;
def tonumberq: tonumber? // .;
. as $in
| reduce range(0; headers|length) as $i ({}; .[headers[$i]] = ($in[$i] | tonumberq) );
def csv2table:
# For jq 1.4, replace the following line by: def trim: .;
def trim: sub("^ +";"") | sub(" +$";"");
split("\n") | map( split(",") | map(trim) );
def csv2json:
csv2table
| .[0] as $headers
| reduce (.[1:][] | select(length > 0) ) as $row
( []; . + [ $row|objectify($headers) ]);
csv2json
示例(假设csv.csv是给定的CSV文本文件):
$ jq -R -s -f csv2json.jq csv.csv
[
{
"name": "john",
"age": 20,
"gender": "male"
},
{
"name": "jane",
"age": 30,
"gender": "female"
},
{
"name": "bob",
"age": 25,
"gender": "male"
}
]
答案 1 :(得分:8)
我玩了一下,然后想出了这个。但是它可能不是最好的方式,我有兴趣看到你的尝试是什么样的,因为毕竟如果我们两个都找到了解决方案我就是确定它好两倍!
但我会从以下内容开始:
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[.]})
变量$doHeaders
控制是否将顶行读作标题行。在你的情况下你想要它是真的,但我把它添加到未来的SO用户,因为,今天我吃了一顿美味的早餐,天气很好,为什么不呢?
小解释:
1). / "\n"
按行分开......
2)map(. / ", ")
...和逗号( Big gotcha:在你的版本中,你会想要使用基于正则表达式的分割,因为这样你就会在逗号内部分开引号也是。我只是用它,因为它很简洁,这使我的解决方案看起来很酷吗?)
3)if $doHeaders then...
这里我们创建一个字符串键或数字的数组,具体取决于第一行中元素的数量以及第一行是否为标题行
4).[if $doHeaders then 1 else 0 end:]
好的,如果它是标题
5)map({($headers[.]): $values[.]})
上面我们遍历前csv中的每一行,并将$values
放入变量,将密钥放入管道。然后我们构建你想要的对象。
当然你会想用几个正则表达式来填补陷阱,但我希望你能在路上开始。
答案 2 :(得分:2)
这是一个假设您使用-s
和-R
选项运行jq的解决方案。
[
[
split("\n")[] # transform csv input into array
| split(", ") # where first element has key names
| select(length==3) # and other elements have values
]
| {h:.[0], v:.[1:][]} # {h:[keys], v:[values]}
| [.h, (.v|map(tonumber?//.))] # [ [keys], [values] ]
| [ transpose[] # [ [key,value], [key,value], ... ]
| {key:.[0], value:.[1]} # [ {"key":key, "value":value}, ... ]
]
| from_entries # { key:value, key:value, ... }
]
示例运行:
jq -s -R -f filter.jq data.csv
示例输出
[
{
"name": "john",
"age": 20,
"gender": "male"
},
{
"name": "jane",
"age": 30,
"gender": "female"
},
{
"name": "bob",
"age": 25,
"gender": "male"
}
]
答案 3 :(得分:2)
从2018年开始,现代的无代码解决方案是使用csvkit
具有csvjson data.csv > data.json
的Python工具。
请参阅他们的文档https://csvkit.readthedocs.io/en/1.0.2/
如果您的脚本必须同时调试jq
和csv
格式,则该工具包也非常方便并且是json
的补充。
您可能还想检查一个名为visidata的强大工具。这是与原始海报相似的screencast case study。您也可以从visidata
答案 4 :(得分:2)
非常简单。使用此input.csv文件
router.put('/:user_id', async (req, res) => {
const { user_id } = req.params;
const gradeFields = {
classCode: req.body.classCode,
gradeLevel: req.body.gradeLevel,
credit: req.body.credit,
mark: req.body.mark
};
try {
// Authenticate with Passport
await passport.authenticate('jwt', { session: false });
// Grab user with this user_id
const existingUser = await UserGrades.findOne({ user: user_id });
if(!existingUser) {
// If user does not exist, throw 404
res.status(404).send("User with this ID does not exist");
}
// Check if user has classCode already on an existing StudentGrade
if(existingUser.StudentGrades.some(sg => sg.classCode === req.body.classCode)) {
res.status(409).send("Student already has grade with this class code.");
}
// Update user record with new StudentGrade and return updates document
const updatedUser = await UserGrades.findOneAndUpdate(
{ user: user_id },
{ $push: { StudentGrades: gradeFields } },
{ new: true }
);
res.status(200).send(updatedUser);
} catch (e) {
console.log('Failed to update user grades', e);
// Unknown server error, send 500
res.status(500).send(e)
}
});
并运行
name,age,gender
john,20,male
jane,30,female
bob,25,male
您将拥有
mlr --c2j --jlistwrap cat input.csv
答案 5 :(得分:0)
这是 jq 的一个相当简单的“单行”版本,适用于“合理”大小的文件,对于非常大的文件,您需要一个不使用 slurp 的版本。我对 jq 还很陌生,我相信有更好的方法来做到这一点(也许只是增加一个索引值而不是存储在数据中)。如果你想让它更短更难读,你可以用 ./"\n" 和 ./"," 替换 "split"。注意:如果你真的需要逗号后的空格可以在“,”上拆分或在逗号分隔后添加 |map(gsub("^\s+|\s+$";""))修剪前导和尾随空白。
#include <stdio.h>
#include <stdlib.h>
int main() {
char* input=calloc(100,sizeof(char));
gets(input);
char *p = strtok(input, " ");
while (p != NULL) {
puts(p);
p = strtok(NULL, " ");
}
return(EXIT_SUCCESS);
}
这是一个评论版本:
jq -Rs 'split("\n")|map(split(",")|to_entries)|.[0] as $header|.[1:]|map(reduce .[] as $item ({};.[$header[$item.key].value]=$item.value))'
顶部非常简单:在换行符上拆分数据,然后将每个元素拆分为逗号,然后 to_entries 会将每个元素转换为键/值条目,并带有键编号(0.. N): {key:#, value:string}
然后它使用 map/reduce 来获取每个元素并使用键/值对的对象替换它,使用编号的键索引回标头以获取标签。对于那些新来减少(像我一样)分号之前的第一个元素是初始化“累加器”(你修改每个元素的东西)所以 .[...] 正在修改累加器和 $item是我们正在操作的对象。
更新:我现在有一个更好的版本,它不使用 slurp,我们不使用 -n 选项,因为它会特别对待第一行:
# jq -Rs
split("\n") | map( split(",") | to_entries ) # split lines, split comma & number
| .[0] as $header # save [0]
| .[1:] # and then drop it
| map( reduce .[] as $item ( {}; .[$header[$item.key].value] = $item.value ) )