Jq का उपयोग करके मनमाने ढंग से सरल JSON को CSV में कैसे बदलें?


105

Jq का उपयोग करके , JSON को मनमाने ढंग से उथली वस्तुओं की एक सरणी को CSV में कैसे बदला जा सकता है?

इस साइट पर बहुत सारे Q & As हैं जो विशिष्ट डेटा मॉडल को कवर करते हैं जो फ़ील्ड्स को हार्ड-कोड करते हैं, लेकिन इस सवाल का जवाब किसी भी JSON को दिया जाना चाहिए, केवल इस प्रतिबंध के साथ कि यह स्केलर गुणों वाली वस्तुओं की एक सरणी है (कोई गहरा / जटिल नहीं / उप-वस्तुएँ, जैसा कि इनका समतल होना एक और प्रश्न है)। परिणाम में फ़ील्ड नाम देने वाली हेडर पंक्ति होनी चाहिए। वरीयता उन उत्तरों को दी जाएगी जो पहली वस्तु के क्षेत्र क्रम को संरक्षित करते हैं, लेकिन यह एक आवश्यकता नहीं है। परिणाम सभी कोशिकाओं को दोहरे-उद्धरणों के साथ संलग्न कर सकते हैं, या केवल उन लोगों को संलग्न कर सकते हैं जिन्हें उद्धृत करने की आवश्यकता होती है (जैसे 'a, b')।

उदाहरण

  1. इनपुट:

    [
        {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"},
        {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"},
        {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"},
        {"code": "AK", "name": "Alaska", "level":"state", "country": "US"}
    ]

    संभावित उत्पादन:

    code,name,level,country
    NSW,New South Wales,state,AU
    AB,Alberta,province,CA
    ABD,Aberdeenshire,council area,GB
    AK,Alaska,state,US

    संभावित उत्पादन:

    "code","name","level","country"
    "NSW","New South Wales","state","AU"
    "AB","Alberta","province","CA"
    "ABD","Aberdeenshire","council area","GB"
    "AK","Alaska","state","US"
  2. इनपुट:

    [
        {"name": "bang", "value": "!", "level": 0},
        {"name": "letters", "value": "a,b,c", "level": 0},
        {"name": "letters", "value": "x,y,z", "level": 1},
        {"name": "bang", "value": "\"!\"", "level": 1}
    ]

    संभावित उत्पादन:

    name,value,level
    bang,!,0
    letters,"a,b,c",0
    letters,"x,y,z",1
    bang,"""!""",0

    संभावित उत्पादन:

    "name","value","level"
    "bang","!","0"
    "letters","a,b,c","0"
    "letters","x,y,z","1"
    "bang","""!""","1"

तीन-प्लस साल बाद ... एक जेनेरिक json2csvहै stackoverflow.com/questions/57242240/…
चोटी

जवाबों:


159

सबसे पहले, अपने ऑब्जेक्ट ऐरे इनपुट में सभी अलग-अलग ऑब्जेक्ट प्रॉपर्टी के नाम वाली एक सरणी प्राप्त करें। वे आपके CSV के कॉलम होंगे:

(map(keys) | add | unique) as $cols

फिर, ऑब्जेक्ट ऐरे इनपुट में प्रत्येक ऑब्जेक्ट के लिए, ऑब्जेक्ट में संबंधित गुणों के लिए आपके द्वारा प्राप्त कॉलम के नाम मैप करें। वे आपके CSV की पंक्तियाँ होंगी।

map(. as $row | $cols | map($row[.])) as $rows

अंत में, पंक्तियों को CSV के हेडर के रूप में पंक्तियों से पहले रखें, और परिणामी पंक्ति स्ट्रीम को @csvफ़िल्टर में पास करें।

$cols, $rows[] | @csv

अब सब एक साथ। -rकच्चे स्ट्रिंग के रूप में परिणाम प्राप्त करने के लिए ध्वज का उपयोग करना याद रखें :

jq -r '(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv'

6
यह अच्छा है कि आपका समाधान संपत्ति के सभी नामों को सभी पंक्तियों से कैप्चर करता है, न कि केवल पहले की तुलना में। मुझे आश्चर्य है कि इसके प्रदर्शन के निहितार्थ बहुत बड़े दस्तावेजों के लिए हैं, हालांकि। पुनश्च यदि आप चाहते हैं, तो आप $rowsइसे केवल इनलाइन करके वैरिएबल असाइनमेंट से छुटकारा पा सकते हैं :(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
जॉर्डन रनिंग

9
धन्यवाद, जॉर्डन! मुझे पता है कि $rowsएक चर को सौंपा जाना नहीं है; मैंने सोचा था कि इसे एक वेरिएबल को सौंपने से स्पष्टीकरण अच्छा हो जाएगा।

3
पंक्ति मान परिवर्तित करने पर विचार करें | स्ट्रिंग नेस्टेड एरेज़ या मैप्स के मामले में।
टीजेआर

अच्छा सुझाव, @TJR हो सकता है कि अगर नेस्टेड संरचनाएँ हों, तो jq को उन में पुनरावृत्ति करना चाहिए और उनके मानों को स्तंभों में बनाना चाहिए
LS

यदि JSON फाइल में थी और CSV के लिए कुछ विशिष्ट डेटा को फ़िल्टर करना चाहते हैं तो यह कैसे अलग होगा?
नव

91

द स्कीनी

jq -r '(.[0] | keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv'

या:

jq -r '(.[0] | keys_unsorted) as $keys | ([$keys] + map([.[ $keys[] ]])) [] | @csv'

विवरण

अलग

विवरणों का वर्णन करना मुश्किल है क्योंकि jq स्ट्रीम-ओरिएंटेड है, जिसका अर्थ है कि यह JSON डेटा के अनुक्रम पर संचालित होता है, बजाय एक मान के। इनपुट JSON स्ट्रीम कुछ आंतरिक प्रकार में परिवर्तित हो जाती है जिसे फ़िल्टर के माध्यम से पारित किया जाता है, फिर प्रोग्राम के अंत में आउटपुट स्ट्रीम में इनकोड किया जाता है। आंतरिक प्रकार JSON द्वारा मॉडलिंग नहीं की जाती है, और एक नामित प्रकार के रूप में मौजूद नहीं है। यह सबसे आसानी से एक नंगे सूचकांक ( .[]) या अल्पविराम ऑपरेटर के उत्पादन की जांच करके प्रदर्शित किया जाता है (इसे सीधे डीबगर के साथ किया जा सकता है, लेकिन यह जेके के आंतरिक डेटा प्रकारों के संदर्भ में होगा, बजाय डॉसन के पीछे वैचारिक डेटा प्रकारों के) ।

$ jc -c '। []' <<< '["a", "b"]'
"ए"
"बी"
$ jc -cn '"a", "b"'
"ए"
"बी"

ध्यान दें कि आउटपुट एक सरणी नहीं है (जो होगा ["a", "b"])। कॉम्पैक्ट आउटपुट ( -cविकल्प) से पता चलता है कि प्रत्येक सरणी तत्व (या ,फ़िल्टर के लिए तर्क ) आउटपुट में एक अलग ऑब्जेक्ट बन जाता है (प्रत्येक अलग लाइन पर है)।

एक स्ट्रीम JSON-seq की तरह है , लेकिन एन्कोडेड होने पर आउटपुट विभाजक के रूप में RS के बजाय नईलाइन का उपयोग करता है । नतीजतन, इस आंतरिक प्रकार को इस उत्तर में जेनेरिक शब्द "अनुक्रम" द्वारा संदर्भित किया गया है, जिसमें एन्कोडेड इनपुट और आउटपुट के लिए "स्ट्रीम" आरक्षित है।

फ़िल्टर का निर्माण

पहले ऑब्जेक्ट की कुंजियों को इसके साथ निकाला जा सकता है:

.[0] | keys_unsorted

आम तौर पर कुंजी को उनके मूल क्रम में रखा जाएगा, लेकिन सटीक क्रम की सुरक्षा की गारंटी नहीं है। नतीजतन, उन्हें उसी क्रम में मान प्राप्त करने के लिए वस्तुओं को अनुक्रमित करने के लिए उपयोग करने की आवश्यकता होगी। यह गलत स्तंभों में मूल्यों को रोक देगा यदि कुछ वस्तुओं का एक अलग कुंजी क्रम है।

पहली पंक्ति के रूप में कुंजियों को आउटपुट करने और उन्हें अनुक्रमण के लिए उपलब्ध कराने के लिए, वे एक चर में संग्रहीत किए जाते हैं। पाइपलाइन का अगला चरण तब इस चर का संदर्भ देता है और शीर्ष लेख को आउटपुट स्ट्रीम में भेजने के लिए अल्पविराम ऑपरेटर का उपयोग करता है।

(.[0] | keys_unsorted) as $keys | $keys, ...

अल्पविराम के बाद की अभिव्यक्ति थोड़ी शामिल है। किसी ऑब्जेक्ट पर अनुक्रमणिका ऑपरेटर स्ट्रिंग्स का एक अनुक्रम ले सकता है (उदा "name", "value"), उन स्ट्रिंग्स के लिए गुण मानों का अनुक्रम लौटाता है। $keysएक सरणी है, अनुक्रम नहीं है, इसलिए []इसे अनुक्रम में बदलने के लिए लागू किया जाता है,

$keys[]

जो तब पारित किया जा सकता है .[]

.[ $keys[] ]

यह भी, एक अनुक्रम का उत्पादन करता है, इसलिए इसे सरणी में बदलने के लिए सरणी कंस्ट्रक्टर का उपयोग किया जाता है।

[.[ $keys[] ]]

इस अभिव्यक्ति को एक ही वस्तु पर लागू किया जाना है। map()बाहरी सरणी में इसे सभी वस्तुओं पर लागू करने के लिए उपयोग किया जाता है:

map([.[ $keys[] ]])

अंतिम रूप से इस चरण के लिए, इसे एक अनुक्रम में परिवर्तित किया जाता है ताकि प्रत्येक आइटम आउटपुट में एक अलग पंक्ति बन जाए।

map([.[ $keys[] ]])[]

क्यों mapकेवल एक सरणी के भीतर एक अनुक्रम में बंडल इसे बाहर खोलना? mapएक सरणी पैदा करता है; .[ $keys[] ]एक अनुक्रम पैदा करता है। mapअनुक्रम से लागू करने से .[ $keys[] ]मूल्यों के अनुक्रम का एक सरणी उत्पन्न होगा, लेकिन चूंकि अनुक्रम एक JSON प्रकार नहीं हैं, इसलिए आपको इसके बजाय सभी मूल्यों वाले एक चपटा सरणी प्राप्त होता है।

["NSW","AU","state","New South Wales","AB","CA","province","Alberta","ABD","GB","council area","Aberdeenshire","AK","US","state","Alaska"]

प्रत्येक वस्तु से मूल्यों को अलग रखने की आवश्यकता होती है, ताकि वे अंतिम आउटपुट में अलग-अलग पंक्तियां बन जाएं।

अंत में, अनुक्रम फॉर्मेटर के माध्यम से पारित किया जाता है @csv

वैकल्पिक

वस्तुओं को जल्दी के बजाय, देर से अलग किया जा सकता है। एक अनुक्रम प्राप्त करने के लिए अल्पविराम ऑपरेटर का उपयोग करने के बजाय (एक अनुक्रम को सही ऑपरेंड के रूप में पारित करना), हेडर अनुक्रम ( $keys) को एक सरणी में लपेटा जा सकता है, और +मानों के सरणी को जोड़ने के लिए उपयोग किया जा सकता है । इसे अभी भी पारित होने से पहले एक अनुक्रम में बदलना होगा @csv


3
क्या आप पहली वस्तु से कुंजी क्रम को संरक्षित करने के keys_unsortedबजाय उपयोग कर सकते हैं keys?
जॉर्डन

2
@outis - धाराओं के बारे में प्रस्तावना कुछ हद तक गलत है। सरल तथ्य यह है कि jq फिल्टर स्ट्रीम-ओरिएंटेड हैं। यही है, कोई भी फ़िल्टर JSON संस्थाओं की एक धारा को स्वीकार कर सकता है, और कुछ फ़िल्टर मानों की एक धारा उत्पन्न कर सकते हैं। किसी स्ट्रीम में आइटमों के बीच कोई "नई लाइन" या कोई अन्य विभाजक नहीं है - यह केवल तब होता है जब वे मुद्रित होते हैं कि एक विभाजक पेश किया जाता है। अपने आप को देखने के लिए, प्रयास करें: jq -n -c 'को कम करें ("a", "b") $ s के रूप में (""; + $ s)'
पीक

2
@ पीक - कृपया इसे उत्तर के रूप में स्वीकार करें, यह अब तक का सबसे पूर्ण और व्यापक है
btk

@btk - मैंने सवाल नहीं पूछा और इसलिए मैं इसे स्वीकार नहीं कर सकता।
पीक

1
@Wyatt: अपने डेटा और उदाहरण इनपुट पर करीब से नज़र डालें। सवाल वस्तुओं की एक सरणी के बारे में है, एक वस्तु नहीं। कोशिश करो [{"a":1,"b":2,"c":3}]
आउट

6

मैंने एक फ़ंक्शन बनाया जो हेडर के साथ सीएसवी के लिए ऑब्जेक्ट्स या सरणियों की एक सरणी आउटपुट करता है। कॉलम हेडर के क्रम में होगा।

def to_csv($headers):
    def _object_to_csv:
        ($headers | @csv),
        (.[] | [.[$headers[]]] | @csv);
    def _array_to_csv:
        ($headers | @csv),
        (.[][:$headers|length] | @csv);
    if .[0]|type == "object"
        then _object_to_csv
        else _array_to_csv
    end;

तो आप इसे इस तरह इस्तेमाल कर सकते हैं:

to_csv([ "code", "name", "level", "country" ])

6

निम्नलिखित फ़िल्टर इसमें थोड़ा अलग है, यह सुनिश्चित करेगा कि प्रत्येक मूल्य एक स्ट्रिंग में परिवर्तित हो जाए। (नोट: jq 1.5+ का उपयोग करें)

# For an array of many objects
jq -f filter.jq (file)

# For many objects (not within array)
jq -s -f filter.jq (file)

फिल्टर: filter.jq

def tocsv($x):
    $x
    |(map(keys)
        |add
        |unique
        |sort
    ) as $cols
    |map(. as $row
        |$cols
        |map($row[.]|tostring)
    ) as $rows
    |$cols,$rows[]
    | @csv;

tocsv(.)

1
यह सरल JSON के लिए अच्छा काम करता है लेकिन JSON के पास नेस्टेड गुणों के बारे में क्या है जो कई स्तरों से नीचे जाते हैं?
अमीर

यह निश्चित रूप से चाबियों को छांटता है। इसके अलावा आउटपुट को uniqueवैसे भी सॉर्ट किया जाता है, इसलिए unique|sortइसे सरल बनाया जा सकता है unique
शिखर

1
@ टीजेआर इस फ़िल्टर का उपयोग करते समय -rविकल्प का उपयोग करके कच्चे आउटपुट पर स्विच करना अनिवार्य है । अन्यथा सभी उद्धरण "अतिरिक्त-बच जाते हैं जो सीएसवी मान्य नहीं है।
टूश

आमिर: नेस्टेड गुण सीएसवी के लिए मैप नहीं करते हैं।
क्रिसमोरिस

2

सैंटियागो के कार्यक्रम का यह संस्करण भी सुरक्षित है लेकिन यह सुनिश्चित करता है कि पहली वस्तु में प्रमुख नाम पहले कॉलम हेडर के रूप में उपयोग किए जाते हैं, उसी क्रम में जैसे वे उस वस्तु में दिखाई देते हैं:

def tocsv:
  if length == 0 then empty
  else
    (.[0] | keys_unsorted) as $keys
    | (map(keys) | add | unique) as $allkeys
    | ($keys + ($allkeys - $keys)) as $cols
    | ($cols, (.[] as $row | $cols | map($row[.])))
    | @csv
  end ;

tocsv
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.