[{"data":1,"prerenderedAt":3089},["ShallowReactive",2],{"/blog/devops-workflows-json-format-jq-ci-cd-lint":3,"navigation-en-us":2302,"banner-en-us":2730,"footer-en-us":2740,"blog-post-authors-en-us-Michael Friedrich":2984,"blog-related-posts-en-us-devops-workflows-json-format-jq-ci-cd-lint":2999,"blog-promotions-en-us":3025,"next-steps-en-us":3079},{"id":4,"title":5,"authors":6,"body":8,"category":2280,"date":2281,"description":2282,"extension":2283,"externalUrl":2284,"faq":2284,"featured":2285,"heroImage":2286,"meta":2287,"navigation":302,"path":2288,"seo":2289,"slug":2294,"stem":2295,"tags":2296,"template":2300,"updatedDate":2284,"__hash__":2301},"blogPosts/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint.md","Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation",[7],"Michael Friedrich",{"type":9,"value":10,"toc":2262},"minimark",[11,16,20,30,38,41,46,49,52,56,59,62,65,69,72,75,78,81,89,92,95,104,107,110,113,117,120,138,141,144,147,150,162,168,192,199,226,230,233,266,283,335,342,366,373,377,385,412,604,617,621,624,653,664,667,723,731,784,794,869,873,879,901,906,1060,1072,1077,1080,1196,1210,1312,1318,1328,1337,1375,1388,1392,1399,1413,1467,1474,1478,1509,1563,1574,1578,1592,1649,1680,1780,1796,1831,1841,1885,1895,1899,1904,1938,1945,1956,1973,2024,2033,2037,2040,2067,2080,2131,2135,2150,2258],[12,13,15],"h2",{"id":14},"what-is-json-linting","What is JSON linting?",[17,18,19],"p",{},"To understand JSON linting, let’s quickly break down the two concepts of JSON and linting.",[17,21,22,29],{},[23,24,25],"em",{},[26,27,28],"strong",{},"JSON"," is an acronym for JavaScript Object Notation, which is a lightweight, text-based, open standard format designed specifically for representing structured data based on the JavaScript object syntax. It is most commonly used for transmitting data in web applications. It parses data faster than XML and is easy for humans to read and write.",[17,31,32,37],{},[23,33,34],{},[26,35,36],{},"Linting"," is a process that automatically checks and analyzes static source code for programming and stylistic errors, bugs and suspicious constructs.",[17,39,40],{},"JSON has become popular because it is human-readable and doesn’t require a complete markup structure like XML. It is easy to analyze into logical syntactic components, especially in JavaScript. It also has many JSON libraries for most programming languages.",[42,43,45],"h3",{"id":44},"benefits-of-json-linting","Benefits of JSON linting",[17,47,48],{},"Finding an error in JSON code can be challenging and time-consuming. The best way to find and correct errors while simultaneously saving time is to use a linting tool. When Json code is copied and pasted into the linting editor, it validates and reformats Json. It is easy to use and supports a wide range of browsers, so applications development with Json coding don’t require a lot of effort to make them browser-compatible.",[17,50,51],{},"JSON linting is an efficient way to reduce errors and it improves the overall quality of the JSON code. This can help accelerate development and reduce costs because errors are discovered earlier.",[42,53,55],{"id":54},"some-common-json-linting-errors","Some common JSON linting errors",[17,57,58],{},"In instances where a JSON transaction fails, the error information is conveyed to the user by the API gateway. By default, the API gateway returns a very basic fault to the client when a message filter has failed.",[17,60,61],{},"One common JSON linting error is parsing. A “parse: unexpected character\" error occurs when passing a value that is not a valid JSON string to the JSON. parse method, for example, a native JavaScript object. To solve the error, make sure to only pass valid JSON strings to the JSON.",[17,63,64],{},"Another common error is NULL or inaccurate data errors, not using the right data type per column or extension for JSON files, and not ensuring every row in the JSON table is in the JSON format.",[42,66,68],{"id":67},"how-to-fix-json-linting-errors","How to fix JSON linting errors",[17,70,71],{},"If you encounter a NULL or inaccurate data error in parsing, the first step is to make sure you use the right data type per column. For example, in the case of “age,” use 12 instead of twelve.",[17,73,74],{},"Also make sure you are using the right extension for JSON files. When using a compressed JSON file, it must end with “json” followed by the extension of the format, such as “.gz.”",[17,76,77],{},"Next, make sure the JSON format is used for every row in the JSON table. Create a table with a delimiter that is not in the input files. Then, run a query equivalent to the return name of the file, row points and the file path for the null NSON rows.",[17,79,80],{},"Sometimes you may find files that are not your source code files, but ones generated by the system when compiling your project. In that instance, when the file has a .js extension, the ESLint needs to exclude that file when searching for errors. One method of doing this is by using ‘IgnorePatterns:’ in .eslintrc.json file either after or before the “rules” tag.",[17,82,83,84,88],{},"“ignorePatterns”: ",[85,86,87],"span",{},"“temp.js”, “**/vendor/*.js”",",",[17,90,91],{},"“rules”: {",[17,93,94],{},"Alternatively, you can create a separate file named‘.eslintignore’ and incorporate the files to be excluded as shown below :\n**/*.js\nIf you opt to correct instead of ignore, look for the error code in the last column. Correct all the errors in one fule and rerun ‘npx eslint . >errfile’ and ensure all the errors of that type are cleared. Then look for the next error code and repeat the procedure until all errors are cleared.",[17,96,97,98,103],{},"Of course, there will be instances when you won’t understand an error, so in that case, open ",[99,100,101],"a",{"href":101,"rel":102},"https://eslint.org/docs/user-guide/getting-started",[]," and type the error code in the ‘Search’ field on the top of the document. There you will find very detailed instructions as to why that error is raised and how to fix it.",[17,105,106],{},"Finally, you can forcibly fix errors automatically while generating the error list using:",[17,108,109],{},"Npx eslintrc . — fix",[17,111,112],{},"This is not recommended until you become more well-versed with lint errors and how to fix them. Also, you should keep a backup of the files you are linting because while fixing errors, certain code may get overwritten, which could cause your program to fail.",[12,114,116],{"id":115},"json-linting-best-practices","JSON linting best practices",[17,118,119],{},"Here are some tips for helping your consumers use your output:",[17,121,122,123,126,127,126,130,133,134,137],{},"First, always enclose the ",[26,124,125],{},"Key"," ",[26,128,129],{},":",[26,131,132],{},"Value"," pair within ",[26,135,136],{},"double quotes",". It may be convenient (not sure how) to generate with Single quotes, but JSON parser don’t like to parse JSON objects with single quotes.",[17,139,140],{},"For numerical values, quotes are optional but it is a good idea to enclose them in double quotes.",[17,142,143],{},"Next, don’t ever use hyphens in your key fields because it breaks python and scala parser. Instead use underscores (_).",[17,145,146],{},"It’s a good idea to always create a root element, especially when you’re creating a complicated JSON.",[17,148,149],{},"Modern web applications come with a REST API which returns JSON. The format needs to be parsed, and often feeds into scripts and service daemons polling the API for automation.",[17,151,152,153,157,158,161],{},"Starting with a new REST API and its endpoints can often be overwhelming. Documentation may suggest looking into a set of SDKs and libraries for various languages, or instruct you to use ",[154,155,156],"code",{},"curl"," or ",[154,159,160],{},"wget"," on the CLI to send a request. Both CLI tools come with a variety of parameters which help to download and print the response string, for example in JSON format.",[17,163,164,165,167],{},"The response string retrieved from ",[154,166,156],{}," may get long and confusing. It can require parsing the JSON format and filtering for a smaller subset of results. This helps with viewing the results on the CLI, and minimizes the data to process in scripts. The following example retrieves all projects from GitLab and returns a paginated result set with the first 20 projects:",[169,170,175],"pre",{"className":171,"code":172,"language":173,"meta":174,"style":174},"language-shell shiki shiki-themes github-light","$ curl \"https://gitlab.com/api/v4/projects\"\n","shell","",[154,176,177],{"__ignoreMap":174},[85,178,181,185,189],{"class":179,"line":180},"line",1,[85,182,184],{"class":183},"s7eDp","$",[85,186,188],{"class":187},"sYBdl"," curl",[85,190,191],{"class":187}," \"https://gitlab.com/api/v4/projects\"\n",[17,193,194],{},[195,196],"img",{"alt":197,"src":198},"Raw JSON as API response","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398186/blog/Content%20Images/devops-workflows-json-format-jq-ci-cd-lint/gitlab_api_response_raw_json.png",[17,200,201,202,207,208,213,214,219,220,225],{},"The ",[99,203,206],{"href":204,"rel":205},"https://docs.gitlab.com/api/#how-to-use-the-api",[],"GitLab REST API documentation"," guides you through the first steps with error handling and authentication. In this blog post, we will be using the ",[99,209,212],{"href":210,"rel":211},"https://docs.gitlab.com/api/#personalproject-access-tokens",[],"Personal Access Token"," as the authentication method. Alternatively, you can use ",[99,215,218],{"href":216,"rel":217},"https://docs.gitlab.com/user/project/settings/project_access_tokens/",[],"project access tokens"," for ",[99,221,224],{"href":222,"rel":223},"https://docs.gitlab.com/api/#authentication",[],"automated authentication"," that avoids the use of personal credentials.",[42,227,229],{"id":228},"rest-api-authentication","REST API authentication",[17,231,232],{},"Since not all endpoints are accessible with anonymous access they might require authentication. Try fetching user profile data with this request:",[169,234,236],{"className":171,"code":235,"language":173,"meta":174,"style":174},"$ curl \"https://gitlab.com/api/v4/user\"\n{\"message\":\"401 Unauthorized\"}\n",[154,237,238,247],{"__ignoreMap":174},[85,239,240,242,244],{"class":179,"line":180},[85,241,184],{"class":183},[85,243,188],{"class":187},[85,245,246],{"class":187}," \"https://gitlab.com/api/v4/user\"\n",[85,248,250,254,257,260,263],{"class":179,"line":249},2,[85,251,253],{"class":252},"sgsFI","{",[85,255,256],{"class":183},"\"message\"",[85,258,129],{"class":259},"sYu0t",[85,261,262],{"class":183},"\"401 Unauthorized\"",[85,264,265],{"class":183},"}\n",[17,267,268,269,272,273,278,279,282],{},"The API request against the ",[154,270,271],{},"/user"," endpoint requires to pass the personal access token into the request, for example, as a request header. To avoid exposing credentials on the terminal, you can export the token and its value into the user's environment. You can automate the variable export with ZSH and the ",[99,274,277],{"href":275,"rel":276},"https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/dotenv",[],".env plugin"," in your shell environment. You can also source the ",[154,280,281],{},".env"," once in the existing shell environment.",[169,284,286],{"className":171,"code":285,"language":173,"meta":174,"style":174},"$ vim ~/.env\n\nexport GITLAB_TOKEN=”...”\n\n$ source ~/.env\n",[154,287,288,298,304,320,325],{"__ignoreMap":174},[85,289,290,292,295],{"class":179,"line":180},[85,291,184],{"class":183},[85,293,294],{"class":187}," vim",[85,296,297],{"class":187}," ~/.env\n",[85,299,300],{"class":179,"line":249},[85,301,303],{"emptyLinePlaceholder":302},true,"\n",[85,305,307,311,314,317],{"class":179,"line":306},3,[85,308,310],{"class":309},"sD7c4","export",[85,312,313],{"class":252}," GITLAB_TOKEN",[85,315,316],{"class":309},"=",[85,318,319],{"class":252},"”...”\n",[85,321,323],{"class":179,"line":322},4,[85,324,303],{"emptyLinePlaceholder":302},[85,326,328,330,333],{"class":179,"line":327},5,[85,329,184],{"class":183},[85,331,332],{"class":187}," source",[85,334,297],{"class":187},[17,336,337,338,341],{},"Scripts and commands being run in your shell environment can reference the ",[154,339,340],{},"$GITLAB_TOKEN"," variable. Try querying the user API endpoint again, with adding the authorization header into the request:",[169,343,345],{"className":171,"code":344,"language":173,"meta":174,"style":174},"$ curl -H \"Authorization: Bearer $GITLAB_TOKEN\" \"https://gitlab.com/api/v4/user\"\n",[154,346,347],{"__ignoreMap":174},[85,348,349,351,353,356,359,361,364],{"class":179,"line":180},[85,350,184],{"class":183},[85,352,188],{"class":187},[85,354,355],{"class":259}," -H",[85,357,358],{"class":187}," \"Authorization: Bearer ",[85,360,340],{"class":252},[85,362,363],{"class":187},"\"",[85,365,246],{"class":187},[17,367,368,369,372],{},"A reminder that only administrators can see the attributes of all users, and the individual can only see their user profile – for example, ",[154,370,371],{},"email"," is hidden from the public domain.",[42,374,376],{"id":375},"how-to-request-responses-in-json","How to request responses in JSON",[17,378,201,379,384],{},[99,380,383],{"href":381,"rel":382},"https://docs.gitlab.com/api/api_resources/",[],"GitLab API provides many resources"," and URL endpoints. You can manage almost anything with the API that you’d otherwise configure using the graphic user interface.",[17,386,387,388,393,394,399,400,405,406,408,409,129],{},"After sending the ",[99,389,392],{"href":390,"rel":391},"https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message",[],"API request",", the ",[99,395,398],{"href":396,"rel":397},"https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message",[],"response message"," contains the body as string, for example as a ",[99,401,404],{"href":402,"rel":403},"https://docs.gitlab.com/api/#content-type",[],"JSON content type",". ",[154,407,156],{}," can provide more information about the response headers which is helpful for debugging. Multiple verbose levels enable the full debug output with ",[154,410,411],{},"-vvv",[169,413,415],{"className":171,"code":414,"language":173,"meta":174,"style":174},"$ curl -vvv \"https://gitlab.com/api/v4/projects\"\n[...]\n* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305\n* ALPN, server accepted to use h2\n* Server certificate:\n*  subject: CN=gitlab.com\n*  start date: Jan 21 00:00:00 2021 GMT\n*  expire date: May 11 23:59:59 2021 GMT\n*  subjectAltName: host \"gitlab.com\" matched cert's \"gitlab.com\"\n*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA\n*  SSL certificate verify ok.\n[...]\n> GET /api/v4/projects HTTP/2\n> Host: gitlab.com\n> User-Agent: curl/7.64.1\n> Accept: */*\n[...]\n\u003C HTTP/2 200\n\u003C date: Mon, 19 Apr 2021 11:25:31 GMT\n\u003C content-type: application/json\n[...]\n[{\"id\":25993690,\"description\":\"project for adding issues\",\"name\":\"project-for-issues-1e1b6d5f938fb240\",\"name_with_namespace\":\"gitlab-qa-sandbox-group / qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6 / project-for-issues-1e1b6d5f938fb240\",\"path\":\"project-for-issues-1e1b6d5f938fb240\",\"path_with_namespace\":\"gitlab-qa-sandbox-group/qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6/project-for-issues-1e1b6d5f938fb240\"\n\n[... JSON content ...]\n\n\"avatar_url\":null,\"web_url\":\"https://gitlab.com/groups/gitlab-qa-sandbox-group/qa-test-2021-04-19-11-12-56-7f3128bd0e41b92f\"}}]\n* Closing connection 0\n",[154,416,417,428,433,441,448,455,468,476,484,501,507,513,518,524,530,536,542,547,553,559,565,570,576,581,587,592,598],{"__ignoreMap":174},[85,418,419,421,423,426],{"class":179,"line":180},[85,420,184],{"class":183},[85,422,188],{"class":187},[85,424,425],{"class":259}," -vvv",[85,427,191],{"class":187},[85,429,430],{"class":179,"line":249},[85,431,432],{"class":252},"[...]\n",[85,434,435,438],{"class":179,"line":306},[85,436,437],{"class":309},"*",[85,439,440],{"class":252}," SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305\n",[85,442,443,445],{"class":179,"line":322},[85,444,437],{"class":309},[85,446,447],{"class":252}," ALPN, server accepted to use h2\n",[85,449,450,452],{"class":179,"line":327},[85,451,437],{"class":309},[85,453,454],{"class":252}," Server certificate:\n",[85,456,458,460,463,465],{"class":179,"line":457},6,[85,459,437],{"class":309},[85,461,462],{"class":252},"  subject: CN",[85,464,316],{"class":309},[85,466,467],{"class":187},"gitlab.com\n",[85,469,471,473],{"class":179,"line":470},7,[85,472,437],{"class":309},[85,474,475],{"class":252},"  start date: Jan 21 00:00:00 2021 GMT\n",[85,477,479,481],{"class":179,"line":478},8,[85,480,437],{"class":309},[85,482,483],{"class":252},"  expire date: May 11 23:59:59 2021 GMT\n",[85,485,487,489,492,495,498],{"class":179,"line":486},9,[85,488,437],{"class":309},[85,490,491],{"class":252},"  subjectAltName: host ",[85,493,494],{"class":187},"\"gitlab.com\"",[85,496,497],{"class":252}," matched cert",[85,499,500],{"class":187},"'s \"gitlab.com\"\n",[85,502,504],{"class":179,"line":503},10,[85,505,506],{"class":187},"*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA\n",[85,508,510],{"class":179,"line":509},11,[85,511,512],{"class":187},"*  SSL certificate verify ok.\n",[85,514,516],{"class":179,"line":515},12,[85,517,432],{"class":187},[85,519,521],{"class":179,"line":520},13,[85,522,523],{"class":187},"> GET /api/v4/projects HTTP/2\n",[85,525,527],{"class":179,"line":526},14,[85,528,529],{"class":187},"> Host: gitlab.com\n",[85,531,533],{"class":179,"line":532},15,[85,534,535],{"class":187},"> User-Agent: curl/7.64.1\n",[85,537,539],{"class":179,"line":538},16,[85,540,541],{"class":187},"> Accept: */*\n",[85,543,545],{"class":179,"line":544},17,[85,546,432],{"class":187},[85,548,550],{"class":179,"line":549},18,[85,551,552],{"class":187},"\u003C HTTP/2 200\n",[85,554,556],{"class":179,"line":555},19,[85,557,558],{"class":187},"\u003C date: Mon, 19 Apr 2021 11:25:31 GMT\n",[85,560,562],{"class":179,"line":561},20,[85,563,564],{"class":187},"\u003C content-type: application/json\n",[85,566,568],{"class":179,"line":567},21,[85,569,432],{"class":187},[85,571,573],{"class":179,"line":572},22,[85,574,575],{"class":187},"[{\"id\":25993690,\"description\":\"project for adding issues\",\"name\":\"project-for-issues-1e1b6d5f938fb240\",\"name_with_namespace\":\"gitlab-qa-sandbox-group / qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6 / project-for-issues-1e1b6d5f938fb240\",\"path\":\"project-for-issues-1e1b6d5f938fb240\",\"path_with_namespace\":\"gitlab-qa-sandbox-group/qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6/project-for-issues-1e1b6d5f938fb240\"\n",[85,577,579],{"class":179,"line":578},23,[85,580,303],{"emptyLinePlaceholder":302},[85,582,584],{"class":179,"line":583},24,[85,585,586],{"class":187},"[... JSON content ...]\n",[85,588,590],{"class":179,"line":589},25,[85,591,303],{"emptyLinePlaceholder":302},[85,593,595],{"class":179,"line":594},26,[85,596,597],{"class":187},"\"avatar_url\":null,\"web_url\":\"https://gitlab.com/groups/gitlab-qa-sandbox-group/qa-test-2021-04-19-11-12-56-7f3128bd0e41b92f\"}}]\n",[85,599,601],{"class":179,"line":600},27,[85,602,603],{"class":187},"* Closing connection 0\n",[17,605,201,606,608,609,612,613,616],{},[154,607,156],{}," command output provides helpful insights into TLS ciphers and versions, the request lines starting with ",[154,610,611],{},">"," and response lines starting with ",[154,614,615],{},"\u003C",". The response body string is encoded as JSON.",[42,618,620],{"id":619},"how-to-see-the-structure-of-the-returned-json","How to see the structure of the returned JSON",[17,622,623],{},"To get a quick look at the structure of the returned JSON file, try these tips:",[625,626,627,635,647],"ul",{},[628,629,630,631,634],"li",{},"Enclose square brackets to identify an array ",[154,632,633],{},"[ …. ]",".",[628,636,637,638,126,643,646],{},"Enclose curly brackets identify a ",[99,639,642],{"href":640,"rel":641},"https://en.wikipedia.org/wiki/Associative_array",[],"dictionary",[154,644,645],{},"{ … }",". Dictionaries are also called associative arrays, maps, etc.",[628,648,649,652],{},[154,650,651],{},"”key”: value"," indicates a key-value pair in a dictionary, which is identified by curly brackets enclosing the key-value pairs.",[17,654,655,656,660,661,634],{},"The values in ",[99,657,28],{"href":658,"rel":659},"https://en.wikipedia.org/wiki/JSON",[]," consist of specific types - a string value is put in double-quotes. Boolean true/false, numbers, and floating-point numbers are also present as types. If a key exists but its value is not set, REST APIs often return ",[154,662,663],{},"null",[17,665,666],{},"Verify the data structure by running \"linters\". Python's JSON module can parse and lint JSON strings. The example below misses a closing square bracket to showcase the error:",[169,668,670],{"className":171,"code":669,"language":173,"meta":174,"style":174},"$ echo '[{\"key\": \"broken\"}' | python -m json.tool\nExpecting object: line 1 column 19 (char 18)\n",[154,671,672,694],{"__ignoreMap":174},[85,673,674,676,679,682,685,688,691],{"class":179,"line":180},[85,675,184],{"class":183},[85,677,678],{"class":187}," echo",[85,680,681],{"class":187}," '[{\"key\": \"broken\"}'",[85,683,684],{"class":309}," |",[85,686,687],{"class":183}," python",[85,689,690],{"class":259}," -m",[85,692,693],{"class":187}," json.tool\n",[85,695,696,699,702,705,708,711,714,717,720],{"class":179,"line":249},[85,697,698],{"class":183},"Expecting",[85,700,701],{"class":187}," object:",[85,703,704],{"class":187}," line",[85,706,707],{"class":259}," 1",[85,709,710],{"class":187}," column",[85,712,713],{"class":259}," 19",[85,715,716],{"class":252}," (char ",[85,718,719],{"class":259},"18",[85,721,722],{"class":252},")\n",[17,724,725,730],{},[99,726,729],{"href":727,"rel":728},"https://stedolan.github.io/jq/",[],"jq"," – a lightweight and flexible CLI processor – can be used as a standalone tool to parse and validate JSON data.",[169,732,734],{"className":171,"code":733,"language":173,"meta":174,"style":174},"$ echo '[{\"key\": \"broken\"}' | jq\nparse error: Unfinished JSON term at EOF at line 2, column 0\n",[154,735,736,749],{"__ignoreMap":174},[85,737,738,740,742,744,746],{"class":179,"line":180},[85,739,184],{"class":183},[85,741,678],{"class":187},[85,743,681],{"class":187},[85,745,684],{"class":309},[85,747,748],{"class":183}," jq\n",[85,750,751,754,757,760,763,766,769,772,774,776,779,781],{"class":179,"line":249},[85,752,753],{"class":183},"parse",[85,755,756],{"class":187}," error:",[85,758,759],{"class":187}," Unfinished",[85,761,762],{"class":187}," JSON",[85,764,765],{"class":187}," term",[85,767,768],{"class":187}," at",[85,770,771],{"class":187}," EOF",[85,773,768],{"class":187},[85,775,704],{"class":187},[85,777,778],{"class":187}," 2,",[85,780,710],{"class":187},[85,782,783],{"class":259}," 0\n",[17,785,786,793],{},[99,787,790,792],{"href":788,"rel":789},"https://stedolan.github.io/jq/download/",[],[154,791,729],{}," is available"," in the package managers of most operating systems.",[169,795,797],{"className":171,"code":796,"language":173,"meta":174,"style":174},"$ brew install jq\n$ apt install jq\n$ dnf install jq\n$ zypper in jq\n$ pacman -S jq\n$ apk add jq\n",[154,798,799,811,822,833,845,857],{"__ignoreMap":174},[85,800,801,803,806,809],{"class":179,"line":180},[85,802,184],{"class":183},[85,804,805],{"class":187}," brew",[85,807,808],{"class":187}," install",[85,810,748],{"class":187},[85,812,813,815,818,820],{"class":179,"line":249},[85,814,184],{"class":183},[85,816,817],{"class":187}," apt",[85,819,808],{"class":187},[85,821,748],{"class":187},[85,823,824,826,829,831],{"class":179,"line":306},[85,825,184],{"class":183},[85,827,828],{"class":187}," dnf",[85,830,808],{"class":187},[85,832,748],{"class":187},[85,834,835,837,840,843],{"class":179,"line":322},[85,836,184],{"class":183},[85,838,839],{"class":187}," zypper",[85,841,842],{"class":187}," in",[85,844,748],{"class":187},[85,846,847,849,852,855],{"class":179,"line":327},[85,848,184],{"class":183},[85,850,851],{"class":187}," pacman",[85,853,854],{"class":259}," -S",[85,856,748],{"class":187},[85,858,859,861,864,867],{"class":179,"line":457},[85,860,184],{"class":183},[85,862,863],{"class":187}," apk",[85,865,866],{"class":187}," add",[85,868,748],{"class":187},[42,870,872],{"id":871},"dive-deep-into-json-data-structures","Dive deep into JSON data structures",[17,874,875,876,878],{},"The true power of ",[154,877,729],{}," lies in how it can be used to parse JSON data:",[880,881,882],"blockquote",{},[17,883,884,886,887,890,891,893,894,893,897,900],{},[154,885,729],{}," is like ",[154,888,889],{},"sed"," for JSON data. It can be used to slice, filter, map, and transform structured data with the same ease that ",[154,892,889],{},", ",[154,895,896],{},"awk",[154,898,899],{},"grep"," etc., let you manipulate text.",[17,902,903,904,634],{},"The output below shows how it looks to run the request against the project API again, but this time, the output is piped to ",[154,905,729],{},[169,907,909],{"className":171,"code":908,"language":173,"meta":174,"style":174},"$ curl \"https://gitlab.com/api/v4/projects\" | jq\n[\n  {\n    \"id\": 25994891,\n    \"description\": \"...\",\n    \"name\": \"...\",\n\n[...]\n\n    \"forks_count\": 0,\n    \"star_count\": 0,\n    \"last_activity_at\": \"2021-04-19T11:50:24.292Z\",\n    \"namespace\": {\n      \"id\": 11528141,\n      \"name\": \"...\",\n\n[...]\n\n    }\n  }\n]\n",[154,910,911,924,929,934,942,956,967,971,975,979,987,994,1006,1014,1022,1033,1037,1041,1045,1050,1055],{"__ignoreMap":174},[85,912,913,915,917,920,922],{"class":179,"line":180},[85,914,184],{"class":183},[85,916,188],{"class":187},[85,918,919],{"class":187}," \"https://gitlab.com/api/v4/projects\"",[85,921,684],{"class":309},[85,923,748],{"class":183},[85,925,926],{"class":179,"line":249},[85,927,928],{"class":252},"[\n",[85,930,931],{"class":179,"line":306},[85,932,933],{"class":252},"  {\n",[85,935,936,939],{"class":179,"line":322},[85,937,938],{"class":187},"    \"id\"",[85,940,941],{"class":252},": 25994891,\n",[85,943,944,947,950,953],{"class":179,"line":327},[85,945,946],{"class":187},"    \"description\"",[85,948,949],{"class":252},": ",[85,951,952],{"class":187},"\"...\"",[85,954,955],{"class":252},",\n",[85,957,958,961,963,965],{"class":179,"line":457},[85,959,960],{"class":187},"    \"name\"",[85,962,949],{"class":252},[85,964,952],{"class":187},[85,966,955],{"class":252},[85,968,969],{"class":179,"line":470},[85,970,303],{"emptyLinePlaceholder":302},[85,972,973],{"class":179,"line":478},[85,974,432],{"class":252},[85,976,977],{"class":179,"line":486},[85,978,303],{"emptyLinePlaceholder":302},[85,980,981,984],{"class":179,"line":503},[85,982,983],{"class":187},"    \"forks_count\"",[85,985,986],{"class":252},": 0,\n",[85,988,989,992],{"class":179,"line":509},[85,990,991],{"class":187},"    \"star_count\"",[85,993,986],{"class":252},[85,995,996,999,1001,1004],{"class":179,"line":515},[85,997,998],{"class":187},"    \"last_activity_at\"",[85,1000,949],{"class":252},[85,1002,1003],{"class":187},"\"2021-04-19T11:50:24.292Z\"",[85,1005,955],{"class":252},[85,1007,1008,1011],{"class":179,"line":520},[85,1009,1010],{"class":187},"    \"namespace\"",[85,1012,1013],{"class":252},": {\n",[85,1015,1016,1019],{"class":179,"line":526},[85,1017,1018],{"class":187},"      \"id\"",[85,1020,1021],{"class":252},": 11528141,\n",[85,1023,1024,1027,1029,1031],{"class":179,"line":532},[85,1025,1026],{"class":187},"      \"name\"",[85,1028,949],{"class":252},[85,1030,952],{"class":187},[85,1032,955],{"class":252},[85,1034,1035],{"class":179,"line":538},[85,1036,303],{"emptyLinePlaceholder":302},[85,1038,1039],{"class":179,"line":544},[85,1040,432],{"class":252},[85,1042,1043],{"class":179,"line":549},[85,1044,303],{"emptyLinePlaceholder":302},[85,1046,1047],{"class":179,"line":555},[85,1048,1049],{"class":252},"    }\n",[85,1051,1052],{"class":179,"line":561},[85,1053,1054],{"class":252},"  }\n",[85,1056,1057],{"class":179,"line":567},[85,1058,1059],{"class":252},"]\n",[17,1061,1062,1063,1068,1069,1071],{},"The first difference is the format of the JSON data structure, so-called ",[99,1064,1067],{"href":1065,"rel":1066},"https://en.wikipedia.org/wiki/Prettyprint",[],"pretty-printed",". New lines and indents in data structure scopes help your eyes and allow you to identify the inner and outer data structures involved. This format is needed to determine which ",[154,1070,729],{}," filters and methods you want to apply next.",[1073,1074,1076],"h4",{"id":1075},"about-arrays-and-dictionaries","About arrays and dictionaries",[17,1078,1079],{},"The set of results from an API often is returned as a list (or \"array\") of items. An item itself can be a single value or a JSON object. The following example mimics the response from the GitLab API and creates an array of dictionaries as a nested result set.",[169,1081,1083],{"className":171,"code":1082,"language":173,"meta":174,"style":174},"$ vim result.json\n[\n  {\n    \"id\": 1,\n    \"name\": \"project1\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"project2\"\n  },\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n]\n",[154,1084,1085,1094,1098,1102,1109,1118,1123,1127,1134,1143,1147,1151,1158,1169,1175,1184,1188,1192],{"__ignoreMap":174},[85,1086,1087,1089,1091],{"class":179,"line":180},[85,1088,184],{"class":183},[85,1090,294],{"class":187},[85,1092,1093],{"class":187}," result.json\n",[85,1095,1096],{"class":179,"line":249},[85,1097,928],{"class":252},[85,1099,1100],{"class":179,"line":306},[85,1101,933],{"class":252},[85,1103,1104,1106],{"class":179,"line":322},[85,1105,938],{"class":187},[85,1107,1108],{"class":252},": 1,\n",[85,1110,1111,1113,1115],{"class":179,"line":327},[85,1112,960],{"class":187},[85,1114,949],{"class":252},[85,1116,1117],{"class":187},"\"project1\"\n",[85,1119,1120],{"class":179,"line":457},[85,1121,1122],{"class":252},"  },\n",[85,1124,1125],{"class":179,"line":470},[85,1126,933],{"class":252},[85,1128,1129,1131],{"class":179,"line":478},[85,1130,938],{"class":187},[85,1132,1133],{"class":252},": 2,\n",[85,1135,1136,1138,1140],{"class":179,"line":486},[85,1137,960],{"class":187},[85,1139,949],{"class":252},[85,1141,1142],{"class":187},"\"project2\"\n",[85,1144,1145],{"class":179,"line":503},[85,1146,1122],{"class":252},[85,1148,1149],{"class":179,"line":509},[85,1150,933],{"class":252},[85,1152,1153,1155],{"class":179,"line":515},[85,1154,938],{"class":187},[85,1156,1157],{"class":252},": 3,\n",[85,1159,1160,1162,1164,1167],{"class":179,"line":520},[85,1161,960],{"class":187},[85,1163,949],{"class":252},[85,1165,1166],{"class":187},"\"project-internal-dev\"",[85,1168,955],{"class":252},[85,1170,1171,1173],{"class":179,"line":526},[85,1172,1010],{"class":187},[85,1174,1013],{"class":252},[85,1176,1177,1179,1181],{"class":179,"line":532},[85,1178,1026],{"class":187},[85,1180,949],{"class":252},[85,1182,1183],{"class":187},"\"🦊\"\n",[85,1185,1186],{"class":179,"line":538},[85,1187,1049],{"class":252},[85,1189,1190],{"class":179,"line":544},[85,1191,1054],{"class":252},[85,1193,1194],{"class":179,"line":549},[85,1195,1059],{"class":252},[17,1197,1198,1199,1202,1203,1205,1206,1209],{},"Use ",[154,1200,1201],{},"cat"," to print the file content on stdout and pipe it into ",[154,1204,729],{},". The outer data structure is an array – use ",[154,1207,1208],{},"-c .[]"," to access and print all items.",[169,1211,1213],{"className":171,"code":1212,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[]'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n",[154,1214,1215,1236,1256,1274],{"__ignoreMap":174},[85,1216,1217,1219,1222,1225,1227,1230,1233],{"class":179,"line":180},[85,1218,184],{"class":183},[85,1220,1221],{"class":187}," cat",[85,1223,1224],{"class":187}," result.json",[85,1226,684],{"class":309},[85,1228,1229],{"class":183}," jq",[85,1231,1232],{"class":259}," -c",[85,1234,1235],{"class":187}," '.[]'\n",[85,1237,1238,1240,1243,1246,1249,1251,1254],{"class":179,"line":249},[85,1239,253],{"class":252},[85,1241,1242],{"class":183},"\"id\"",[85,1244,1245],{"class":183},":1,",[85,1247,1248],{"class":183},"\"name\"",[85,1250,129],{"class":259},[85,1252,1253],{"class":183},"\"project1\"",[85,1255,265],{"class":183},[85,1257,1258,1260,1262,1265,1267,1269,1272],{"class":179,"line":306},[85,1259,253],{"class":252},[85,1261,1242],{"class":183},[85,1263,1264],{"class":183},":2,",[85,1266,1248],{"class":183},[85,1268,129],{"class":259},[85,1270,1271],{"class":183},"\"project2\"",[85,1273,265],{"class":183},[85,1275,1276,1278,1280,1283,1285,1287,1289,1291,1294,1296,1298,1300,1303,1306,1309],{"class":179,"line":322},[85,1277,253],{"class":252},[85,1279,1242],{"class":183},[85,1281,1282],{"class":183},":3,",[85,1284,1248],{"class":183},[85,1286,129],{"class":259},[85,1288,1166],{"class":183},[85,1290,88],{"class":183},[85,1292,1293],{"class":183},"\"namespace\"",[85,1295,129],{"class":259},[85,1297,253],{"class":187},[85,1299,363],{"class":252},[85,1301,1302],{"class":183},"name",[85,1304,1305],{"class":183},"\":\"",[85,1307,1308],{"class":183},"🦊",[85,1310,1311],{"class":183},"\"}}\n",[42,1313,1315,1316],{"id":1314},"how-to-filter-data-structures-with-jq","How to filter data structures with ",[154,1317,729],{},[17,1319,1320,1321,1324,1325,1327],{},"Filter items by passing ",[154,1322,1323],{},"| select (...)"," to ",[154,1326,729],{},". The filter takes a lambda callback function as a comparator condition. When the item matches the condition, it is returned to the caller.",[17,1329,1330,1331,1333,1334,129],{},"Use the dot indexer ",[154,1332,634],{}," to access dictionary keys and their values. Try to filter for all items where the name is ",[154,1335,1336],{},"project2",[169,1338,1340],{"className":171,"code":1339,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[] | select (.name == \"project2\")'\n{\"id\":2,\"name\":\"project2\"}\n",[154,1341,1342,1359],{"__ignoreMap":174},[85,1343,1344,1346,1348,1350,1352,1354,1356],{"class":179,"line":180},[85,1345,184],{"class":183},[85,1347,1221],{"class":187},[85,1349,1224],{"class":187},[85,1351,684],{"class":309},[85,1353,1229],{"class":183},[85,1355,1232],{"class":259},[85,1357,1358],{"class":187}," '.[] | select (.name == \"project2\")'\n",[85,1360,1361,1363,1365,1367,1369,1371,1373],{"class":179,"line":249},[85,1362,253],{"class":252},[85,1364,1242],{"class":183},[85,1366,1264],{"class":183},[85,1368,1248],{"class":183},[85,1370,129],{"class":259},[85,1372,1271],{"class":183},[85,1374,265],{"class":183},[17,1376,1377,1378,1381,1382,1385,1386,634],{},"Practice this example by selecting the ",[154,1379,1380],{},"id"," with the value ",[154,1383,1384],{},"2"," instead of the ",[154,1387,1302],{},[1073,1389,1391],{"id":1390},"filter-with-matching-a-string","Filter with matching a string",[17,1393,1394,1395,1398],{},"During tests, you may need to match different patterns instead of knowing the full name. Think of projects that match a specific path or are located in a group where you only know the prefix. Simple string matches can be achieved with the ",[154,1396,1397],{},"| contains (...)"," function. It allows you to check whether the given string is inside the target string – which requires the selected attribute to be of the string type.",[17,1400,1401,1402,1405,1406,1409,1410,634],{},"For a filter with the select chain, the comparison condition needs to be changed from the equal operator ",[154,1403,1404],{},"=="," to checking the attribute ",[154,1407,1408],{},".name"," with ",[154,1411,1412],{},"| contains (\"dev\")",[169,1414,1416],{"className":171,"code":1415,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[] | select (.name | contains (\"dev\") )'\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n",[154,1417,1418,1435],{"__ignoreMap":174},[85,1419,1420,1422,1424,1426,1428,1430,1432],{"class":179,"line":180},[85,1421,184],{"class":183},[85,1423,1221],{"class":187},[85,1425,1224],{"class":187},[85,1427,684],{"class":309},[85,1429,1229],{"class":183},[85,1431,1232],{"class":259},[85,1433,1434],{"class":187}," '.[] | select (.name | contains (\"dev\") )'\n",[85,1436,1437,1439,1441,1443,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1465],{"class":179,"line":249},[85,1438,253],{"class":252},[85,1440,1242],{"class":183},[85,1442,1282],{"class":183},[85,1444,1248],{"class":183},[85,1446,129],{"class":259},[85,1448,1166],{"class":183},[85,1450,88],{"class":183},[85,1452,1293],{"class":183},[85,1454,129],{"class":259},[85,1456,253],{"class":187},[85,1458,363],{"class":252},[85,1460,1302],{"class":183},[85,1462,1305],{"class":183},[85,1464,1308],{"class":183},[85,1466,1311],{"class":183},[17,1468,1469,1470,1473],{},"Simple matches can be achieved with the ",[154,1471,1472],{},"contains"," function.",[1073,1475,1477],{"id":1476},"filter-with-matching-regular-expressions","Filter with matching regular expressions",[17,1479,1480,1481,1483,1484,1489,1490,1493,1494,1497,1498,1501,1502,1505,1506,1508],{},"For advanced string pattern matching, it is recommended to use regular expressions. ",[154,1482,729],{}," provides the ",[99,1485,1488],{"href":1486,"rel":1487},"https://stedolan.github.io/jq/manual/#RegularexpressionsPCRE",[],"test function for this use case",". Try to filter for all projects which end with a number, represented by ",[154,1491,1492],{},"\\d+",". Note that the backslash ",[154,1495,1496],{},"\\"," needs to be escaped as ",[154,1499,1500],{},"\\\\"," for shell execution. ",[154,1503,1504],{},"^"," tests for beginning of the string, ",[154,1507,184],{}," is the ending check.",[169,1510,1512],{"className":171,"code":1511,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[] | select (.name | test (\"^project\\\\d+$\") )'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n",[154,1513,1514,1531,1547],{"__ignoreMap":174},[85,1515,1516,1518,1520,1522,1524,1526,1528],{"class":179,"line":180},[85,1517,184],{"class":183},[85,1519,1221],{"class":187},[85,1521,1224],{"class":187},[85,1523,684],{"class":309},[85,1525,1229],{"class":183},[85,1527,1232],{"class":259},[85,1529,1530],{"class":187}," '.[] | select (.name | test (\"^project\\\\d+$\") )'\n",[85,1532,1533,1535,1537,1539,1541,1543,1545],{"class":179,"line":249},[85,1534,253],{"class":252},[85,1536,1242],{"class":183},[85,1538,1245],{"class":183},[85,1540,1248],{"class":183},[85,1542,129],{"class":259},[85,1544,1253],{"class":183},[85,1546,265],{"class":183},[85,1548,1549,1551,1553,1555,1557,1559,1561],{"class":179,"line":306},[85,1550,253],{"class":252},[85,1552,1242],{"class":183},[85,1554,1264],{"class":183},[85,1556,1248],{"class":183},[85,1558,129],{"class":259},[85,1560,1271],{"class":183},[85,1562,265],{"class":183},[17,1564,1565,1566,1571,1572,634],{},"Tip: You can ",[99,1567,1570],{"href":1568,"rel":1569},"https://regex101.com/",[],"test and build the regular expression with regex101"," before test-driving it with ",[154,1573,729],{},[1073,1575,1577],{"id":1576},"access-nested-values","Access nested values",[17,1579,1580,1581,1583,1584,1587,1588,1591],{},"Key value pairs in a dictionary may have a dictionary or array as a value. ",[154,1582,729],{}," filters need to take this factor into account when filtering or transforming the result. The example data structure provides ",[154,1585,1586],{},"project-internal-dev"," which has the key ",[154,1589,1590],{},"namespace"," and a value of a dictionary type.",[169,1593,1595],{"className":171,"code":1594,"language":173,"meta":174,"style":174},"\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n\n",[154,1596,1597,1601,1605,1614,1623,1632,1641,1645],{"__ignoreMap":174},[85,1598,1599],{"class":179,"line":180},[85,1600,303],{"emptyLinePlaceholder":302},[85,1602,1603],{"class":179,"line":249},[85,1604,933],{"class":252},[85,1606,1607,1609,1611],{"class":179,"line":306},[85,1608,938],{"class":183},[85,1610,129],{"class":259},[85,1612,1613],{"class":187}," 3,\n",[85,1615,1616,1618,1620],{"class":179,"line":322},[85,1617,960],{"class":183},[85,1619,129],{"class":259},[85,1621,1622],{"class":187}," \"project-internal-dev\",\n",[85,1624,1625,1627,1629],{"class":179,"line":327},[85,1626,1010],{"class":183},[85,1628,129],{"class":259},[85,1630,1631],{"class":187}," {\n",[85,1633,1634,1636,1638],{"class":179,"line":457},[85,1635,1026],{"class":183},[85,1637,129],{"class":259},[85,1639,1640],{"class":187}," \"🦊\"\n",[85,1642,1643],{"class":179,"line":470},[85,1644,1049],{"class":252},[85,1646,1647],{"class":179,"line":478},[85,1648,1054],{"class":252},[17,1650,1651,1653,1654,1659,1660,1663,1664,1667,1668,1670,1671,1673,1674,1676,1677,1679],{},[154,1652,729],{}," allows the user to specify the ",[99,1655,1658],{"href":1656,"rel":1657},"https://stedolan.github.io/jq/manual/#TypesandValues",[],"array and dictionary types"," as ",[154,1661,1662],{},"[]"," and ",[154,1665,1666],{},"{}"," to be used in select chains with greater and less than comparisons. The ",[154,1669,1662],{}," brackets select filters for non-empty dictionaries for the ",[154,1672,1590],{}," attribute, while the ",[154,1675,1666],{}," brackets select for all ",[154,1678,663],{}," (raw JSON) values.",[169,1681,1683],{"className":171,"code":1682,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[] | select (.namespace >={} )'\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n$ cat result.json | jq -c '.[] | select (.namespace \u003C={} )'\n{\"id\":1,\"name\":\"project1\"}\n{\"id\":2,\"name\":\"project2\"}\n",[154,1684,1685,1702,1734,1738,1743,1763],{"__ignoreMap":174},[85,1686,1687,1689,1691,1693,1695,1697,1699],{"class":179,"line":180},[85,1688,184],{"class":183},[85,1690,1221],{"class":187},[85,1692,1224],{"class":187},[85,1694,684],{"class":309},[85,1696,1229],{"class":183},[85,1698,1232],{"class":259},[85,1700,1701],{"class":187}," '.[] | select (.namespace >={} )'\n",[85,1703,1704,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732],{"class":179,"line":249},[85,1705,253],{"class":252},[85,1707,1242],{"class":183},[85,1709,1282],{"class":183},[85,1711,1248],{"class":183},[85,1713,129],{"class":259},[85,1715,1166],{"class":183},[85,1717,88],{"class":183},[85,1719,1293],{"class":183},[85,1721,129],{"class":259},[85,1723,253],{"class":187},[85,1725,363],{"class":252},[85,1727,1302],{"class":183},[85,1729,1305],{"class":183},[85,1731,1308],{"class":183},[85,1733,1311],{"class":183},[85,1735,1736],{"class":179,"line":306},[85,1737,303],{"emptyLinePlaceholder":302},[85,1739,1740],{"class":179,"line":322},[85,1741,1742],{"class":183},"$ cat result.json | jq -c '.[] | select (.namespace \u003C={} )'\n",[85,1744,1745,1748,1750,1753,1755,1757,1760],{"class":179,"line":327},[85,1746,1747],{"class":183},"{\"",[85,1749,1380],{"class":183},[85,1751,1752],{"class":183},"\":1,\"",[85,1754,1302],{"class":183},[85,1756,1305],{"class":183},[85,1758,1759],{"class":183},"project1",[85,1761,1762],{"class":183},"\"}\n",[85,1764,1765,1767,1769,1772,1774,1776,1778],{"class":179,"line":457},[85,1766,1747],{"class":183},[85,1768,1380],{"class":183},[85,1770,1771],{"class":183},"\":2,\"",[85,1773,1302],{"class":183},[85,1775,1305],{"class":183},[85,1777,1336],{"class":183},[85,1779,1762],{"class":183},[17,1781,1782,1783,1785,1786,1788,1789,1791,1792,1795],{},"These methods can be used to access the name attribute of the namespace, but only if the namespace contains values. Tip: You can chain multiple ",[154,1784,729],{}," calls by piping the result into another ",[154,1787,729],{}," call. ",[154,1790,1408],{}," is a subkey of the primary ",[154,1793,1794],{},".namespace"," key.",[169,1797,1799],{"className":171,"code":1798,"language":173,"meta":174,"style":174},"$ cat result.json | jq -c '.[] | select (.namespace >={} )' | jq -c '.namespace.name'\n\"🦊\"\n",[154,1800,1801,1827],{"__ignoreMap":174},[85,1802,1803,1805,1807,1809,1811,1813,1815,1818,1820,1822,1824],{"class":179,"line":180},[85,1804,184],{"class":183},[85,1806,1221],{"class":187},[85,1808,1224],{"class":187},[85,1810,684],{"class":309},[85,1812,1229],{"class":183},[85,1814,1232],{"class":259},[85,1816,1817],{"class":187}," '.[] | select (.namespace >={} )'",[85,1819,684],{"class":309},[85,1821,1229],{"class":183},[85,1823,1232],{"class":259},[85,1825,1826],{"class":187}," '.namespace.name'\n",[85,1828,1829],{"class":179,"line":249},[85,1830,1183],{"class":183},[17,1832,1833,1834,1837,1838,1840],{},"The additional select command with non-empty namespaces ensures that only initialized values for ",[154,1835,1836],{},".namespace.name"," are returned. This is a safety check, and avoids receiving ",[154,1839,663],{}," values in the result you would need to filter again.",[169,1842,1844],{"className":171,"code":1843,"language":173,"meta":174,"style":174},"$ cat result.json| jq -c '.[]' | jq -c '.namespace.name'\nnull\nnull\n\"🦊\"\n",[154,1845,1846,1872,1877,1881],{"__ignoreMap":174},[85,1847,1848,1850,1852,1854,1857,1859,1861,1864,1866,1868,1870],{"class":179,"line":180},[85,1849,184],{"class":183},[85,1851,1221],{"class":187},[85,1853,1224],{"class":187},[85,1855,1856],{"class":309},"|",[85,1858,1229],{"class":183},[85,1860,1232],{"class":259},[85,1862,1863],{"class":187}," '.[]'",[85,1865,684],{"class":309},[85,1867,1229],{"class":183},[85,1869,1232],{"class":259},[85,1871,1826],{"class":187},[85,1873,1874],{"class":179,"line":249},[85,1875,1876],{"class":183},"null\n",[85,1878,1879],{"class":179,"line":306},[85,1880,1876],{"class":183},[85,1882,1883],{"class":179,"line":322},[85,1884,1183],{"class":183},[17,1886,1887,1888,1891,1892,1894],{},"By using the additional check with ",[154,1889,1890],{},"| select (.namespace >={} )",", you only get the expected results and do not have to filter empty ",[154,1893,663],{}," values.",[42,1896,1898],{"id":1897},"how-to-expand-the-gitlab-endpoint-response","How to expand the GitLab endpoint response",[17,1900,1901,1902,634],{},"Save the result from the API projects call and retry the examples above with ",[154,1903,729],{},[169,1905,1907],{"className":171,"code":1906,"language":173,"meta":174,"style":174},"$ curl \"https://gitlab.com/api/v4/projects\" -o result.json 2&>1 >/dev/null\n",[154,1908,1909],{"__ignoreMap":174},[85,1910,1911,1913,1915,1917,1920,1922,1925,1928,1930,1933,1935],{"class":179,"line":180},[85,1912,184],{"class":183},[85,1914,188],{"class":187},[85,1916,919],{"class":187},[85,1918,1919],{"class":259}," -o",[85,1921,1224],{"class":187},[85,1923,1924],{"class":259}," 2",[85,1926,1927],{"class":252},"&",[85,1929,611],{"class":309},[85,1931,1932],{"class":252},"1 ",[85,1934,611],{"class":309},[85,1936,1937],{"class":252},"/dev/null\n",[42,1939,1941,1942,1944],{"id":1940},"validate-cicd-yaml-with-jq-for-git-hooks","Validate CI/CD YAML with ",[154,1943,729],{}," for Git hooks",[17,1946,1947,1948,1955],{},"While writing this blog post, I learned that you can ",[99,1949,1952,1953],{"href":1950,"rel":1951},"https://docs.gitlab.com/api/lint/#escape-yaml-for-json-encoding",[],"escape and encode YAML into JSON with ",[154,1954,729],{},". This trick comes in handy when automating YAML linting on the CLI, for example as a Git pre-commit hook.",[17,1957,1958,1959,1964,1965,1968,1969,1972],{},"Let’s take a look at the simplest way to test GitLab CI/CD from our ",[99,1960,1963],{"href":1961,"rel":1962},"https://gitlab.com/gitlab-da/swiss-meetup-2021-jan#resources",[],"community meetup workshops",". A common mistake with the first steps of the process can be missing the two spaces indent or missing whitespace between the dash and following command. The following examples use ",[154,1966,1967],{},".gitlab-ci.error.yml"," as a filename to showcase errors and ",[154,1970,1971],{},".gitlab-ci.main.yml"," for working examples.",[169,1974,1976],{"className":171,"code":1975,"language":173,"meta":174,"style":174},"$ vim .gitlab-ci.error.yml\n\nimage: alpine:latest\n\ntest:\nscript:\n  -exit 1\n\n",[154,1977,1978,1987,1991,1999,2003,2011,2016],{"__ignoreMap":174},[85,1979,1980,1982,1984],{"class":179,"line":180},[85,1981,184],{"class":183},[85,1983,294],{"class":187},[85,1985,1986],{"class":187}," .gitlab-ci.error.yml\n",[85,1988,1989],{"class":179,"line":249},[85,1990,303],{"emptyLinePlaceholder":302},[85,1992,1993,1996],{"class":179,"line":306},[85,1994,1995],{"class":183},"image:",[85,1997,1998],{"class":187}," alpine:latest\n",[85,2000,2001],{"class":179,"line":322},[85,2002,303],{"emptyLinePlaceholder":302},[85,2004,2005,2008],{"class":179,"line":327},[85,2006,2007],{"class":259},"test",[85,2009,2010],{"class":187},":\n",[85,2012,2013],{"class":179,"line":457},[85,2014,2015],{"class":183},"script:\n",[85,2017,2018,2021],{"class":179,"line":470},[85,2019,2020],{"class":183},"  -exit",[85,2022,2023],{"class":259}," 1\n",[17,2025,2026,2027,2032],{},"Committing the change and waiting for the CI/CD pipeline to validate at runtime can be time-consuming. The ",[99,2028,2031],{"href":2029,"rel":2030},"https://docs.gitlab.com/api/lint/#validate-the-ci-yaml-configuration",[],"GitLab API provides a resource endpoint /ci/lint",". A POST request with JSON-encoded YAML content will return a linting result faster.",[1073,2034,2036],{"id":2035},"parse-cicd-yaml-into-json-with-jq","Parse CI/CD YAML into JSON with jq",[17,2038,2039],{},"You can use jq to parse the raw YAML string into JSON:",[169,2041,2043],{"className":171,"code":2042,"language":173,"meta":174,"style":174},"$ jq --raw-input --slurp \u003C .gitlab-ci.error.yml\n\"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\\n\"\n",[154,2044,2045,2062],{"__ignoreMap":174},[85,2046,2047,2049,2051,2054,2057,2060],{"class":179,"line":180},[85,2048,184],{"class":183},[85,2050,1229],{"class":187},[85,2052,2053],{"class":259}," --raw-input",[85,2055,2056],{"class":259}," --slurp",[85,2058,2059],{"class":309}," \u003C",[85,2061,1986],{"class":187},[85,2063,2064],{"class":179,"line":249},[85,2065,2066],{"class":183},"\"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\\n\"\n",[17,2068,201,2069,2072,2073,2076,2077,2079],{},[154,2070,2071],{},"/ci/lint"," API endpoint requires a JSON dictionary with ",[154,2074,2075],{},"content"," as key, and the raw YAML string as a value. You can use ",[154,2078,729],{}," to format the input by using the arg parser:",[169,2081,2083],{"className":171,"code":2082,"language":173,"meta":174,"style":174},"§ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml'\n{\n  \"content\": \"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\"\n}\n",[154,2084,2085,2112,2117,2127],{"__ignoreMap":174},[85,2086,2087,2090,2092,2095,2098,2101,2104,2106,2109],{"class":179,"line":180},[85,2088,2089],{"class":183},"§",[85,2091,1229],{"class":187},[85,2093,2094],{"class":259}," --null-input",[85,2096,2097],{"class":259}," --arg",[85,2099,2100],{"class":187}," yaml",[85,2102,2103],{"class":187}," \"$(",[85,2105,615],{"class":309},[85,2107,2108],{"class":187},".gitlab-ci.error.yml)\"",[85,2110,2111],{"class":187}," '.content=$yaml'\n",[85,2113,2114],{"class":179,"line":249},[85,2115,2116],{"class":252},"{\n",[85,2118,2119,2122,2124],{"class":179,"line":306},[85,2120,2121],{"class":183},"  \"content\"",[85,2123,129],{"class":259},[85,2125,2126],{"class":187}," \"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\"\n",[85,2128,2129],{"class":179,"line":322},[85,2130,265],{"class":252},[1073,2132,2134],{"id":2133},"send-post-request-to-cilint","Send POST request to /ci/lint",[17,2136,2137,2138,2142,2143,2146,2147,2149],{},"The next building block is to ",[99,2139,2141],{"href":2029,"rel":2140},[],"send a POST request to the /ci/lint",". The request needs to specify the ",[154,2144,2145],{},"Content-Type"," header for the body. With using the pipe ",[154,2148,1856],{}," character, the JSON-encoded YAML configuration is fed into the curl command call.",[169,2151,2153],{"className":171,"code":2152,"language":173,"meta":174,"style":174},"$ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml' \\\n| curl \"https://gitlab.com/api/v4/ci/lint?include_merged_yaml=true\" \\\n--header 'Content-Type: application/json' --data @-\n{\"status\":\"invalid\",\"errors\":[\"jobs test config should implement a script: or a trigger: keyword\",\"jobs script config should implement a script: or a trigger: keyword\",\"jobs config should contain at least one visible job\"],\"warnings\":[],\"merged_yaml\":\"\n",[154,2154,2155,2179,2190,2204],{"__ignoreMap":174},[85,2156,2157,2159,2161,2163,2165,2167,2169,2171,2173,2176],{"class":179,"line":180},[85,2158,184],{"class":183},[85,2160,1229],{"class":187},[85,2162,2094],{"class":259},[85,2164,2097],{"class":259},[85,2166,2100],{"class":187},[85,2168,2103],{"class":187},[85,2170,615],{"class":309},[85,2172,2108],{"class":187},[85,2174,2175],{"class":187}," '.content=$yaml'",[85,2177,2178],{"class":259}," \\\n",[85,2180,2181,2183,2185,2188],{"class":179,"line":249},[85,2182,1856],{"class":309},[85,2184,188],{"class":183},[85,2186,2187],{"class":187}," \"https://gitlab.com/api/v4/ci/lint?include_merged_yaml=true\"",[85,2189,2178],{"class":259},[85,2191,2192,2195,2198,2201],{"class":179,"line":306},[85,2193,2194],{"class":252},"--header ",[85,2196,2197],{"class":187},"'Content-Type: application/json'",[85,2199,2200],{"class":259}," --data",[85,2202,2203],{"class":187}," @-\n",[85,2205,2206,2208,2211,2213,2216,2218,2221,2223,2226,2229,2231,2234,2236,2239,2242,2245,2247,2250,2253,2255],{"class":179,"line":322},[85,2207,253],{"class":252},[85,2209,2210],{"class":183},"\"status\"",[85,2212,129],{"class":259},[85,2214,2215],{"class":183},"\"invalid\"",[85,2217,88],{"class":183},[85,2219,2220],{"class":183},"\"errors\"",[85,2222,129],{"class":259},[85,2224,2225],{"class":187},"[",[85,2227,2228],{"class":183},"\"jobs test config should implement a script: or a trigger: keyword\"",[85,2230,88],{"class":183},[85,2232,2233],{"class":183},"\"jobs script config should implement a script: or a trigger: keyword\"",[85,2235,88],{"class":183},[85,2237,2238],{"class":183},"\"jobs config should contain at least one visible job\"",[85,2240,2241],{"class":183},"],",[85,2243,2244],{"class":183},"\"warnings\"",[85,2246,129],{"class":259},[85,2248,2249],{"class":187},"[],",[85,2251,2252],{"class":183},"\"merged_yaml\"",[85,2254,129],{"class":259},[85,2256,2257],{"class":183},"\"\n",[2259,2260,2261],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}",{"title":174,"searchDepth":249,"depth":249,"links":2263},[2264,2269],{"id":14,"depth":249,"text":15,"children":2265},[2266,2267,2268],{"id":44,"depth":306,"text":45},{"id":54,"depth":306,"text":55},{"id":67,"depth":306,"text":68},{"id":115,"depth":249,"text":116,"children":2270},[2271,2272,2273,2274,2275,2277,2278],{"id":228,"depth":306,"text":229},{"id":375,"depth":306,"text":376},{"id":619,"depth":306,"text":620},{"id":871,"depth":306,"text":872},{"id":1314,"depth":306,"text":2276},"How to filter data structures with jq",{"id":1897,"depth":306,"text":1898},{"id":1940,"depth":306,"text":2279},"Validate CI/CD YAML with jq for Git hooks","engineering","2021-04-21","Learn how to filter in JSON data structures and interact with the REST API. Use the GitLab API to lint your CI/CD configuration and dive into Git hooks speeding up your workflows.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681979/Blog/Hero%20Images/gert-boers-unsplash.jpg",{},"/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint",{"title":2290,"description":2282,"ogTitle":2290,"ogDescription":2282,"noIndex":2285,"ogImage":2286,"ogUrl":2291,"ogSiteName":2292,"ogType":2293,"canonicalUrls":2291},"JSON formatting and CI/CD linting tips for DevOps workflows","https://about.gitlab.com/blog/devops-workflows-json-format-jq-ci-cd-lint","https://about.gitlab.com","article","devops-workflows-json-format-jq-ci-cd-lint","en-us/blog/devops-workflows-json-format-jq-ci-cd-lint",[2297,2298,2299],"CI","DevOps","tutorial","BlogPost","fb3o5CvvwcC8bohzdnvkPy_b2psGVQ6XZOZZscVJD54",{"logo":2303,"freeTrial":2308,"sales":2313,"login":2318,"items":2323,"search":2650,"minimal":2681,"duo":2700,"switchNav":2709,"pricingDeployment":2720},{"config":2304},{"href":2305,"dataGaName":2306,"dataGaLocation":2307},"/","gitlab logo","header",{"text":2309,"config":2310},"Get free trial",{"href":2311,"dataGaName":2312,"dataGaLocation":2307},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":2314,"config":2315},"Request a demo",{"href":2316,"dataGaName":2317,"dataGaLocation":2307},"/sales/?contact-topic=request-demo","sales",{"text":2319,"config":2320},"Sign in",{"href":2321,"dataGaName":2322,"dataGaLocation":2307},"https://gitlab.com/users/sign_in/","sign in",[2324,2353,2453,2458,2572,2628],{"text":2325,"config":2326,"menu":2328},"Platform",{"dataNavLevelOne":2327},"platform",{"type":2329,"columns":2330},"cards",[2331,2337,2345],{"title":2325,"description":2332,"link":2333},"The intelligent orchestration platform for DevSecOps",{"text":2334,"config":2335},"Explore our Platform",{"href":2336,"dataGaName":2327,"dataGaLocation":2307},"/platform/",{"title":2338,"description":2339,"link":2340},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":2341,"config":2342},"Meet GitLab Duo",{"href":2343,"dataGaName":2344,"dataGaLocation":2307},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":2346,"description":2347,"link":2348},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":2349,"config":2350},"Learn more",{"href":2351,"dataGaName":2352,"dataGaLocation":2307},"/why-gitlab/","why gitlab",{"text":2354,"left":302,"config":2355,"menu":2357},"Product",{"dataNavLevelOne":2356},"solutions",{"type":2358,"link":2359,"columns":2363,"feature":2432},"lists",{"text":2360,"config":2361},"View all Solutions",{"href":2362,"dataGaName":2356,"dataGaLocation":2307},"/solutions/",[2364,2388,2411],{"title":2365,"description":2366,"link":2367,"items":2372},"Automation","CI/CD and automation to accelerate deployment",{"config":2368},{"icon":2369,"href":2370,"dataGaName":2371,"dataGaLocation":2307},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[2373,2377,2380,2384],{"text":2374,"config":2375},"CI/CD",{"href":2376,"dataGaLocation":2307,"dataGaName":2374},"/solutions/continuous-integration/",{"text":2338,"config":2378},{"href":2343,"dataGaLocation":2307,"dataGaName":2379},"gitlab duo agent platform - product menu",{"text":2381,"config":2382},"Source Code Management",{"href":2383,"dataGaLocation":2307,"dataGaName":2381},"/solutions/source-code-management/",{"text":2385,"config":2386},"Automated Software Delivery",{"href":2370,"dataGaLocation":2307,"dataGaName":2387},"Automated software delivery",{"title":2389,"description":2390,"link":2391,"items":2396},"Security","Deliver code faster without compromising security",{"config":2392},{"href":2393,"dataGaName":2394,"dataGaLocation":2307,"icon":2395},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[2397,2401,2406],{"text":2398,"config":2399},"Application Security Testing",{"href":2393,"dataGaName":2400,"dataGaLocation":2307},"Application security testing",{"text":2402,"config":2403},"Software Supply Chain Security",{"href":2404,"dataGaLocation":2307,"dataGaName":2405},"/solutions/supply-chain/","Software supply chain security",{"text":2407,"config":2408},"Software Compliance",{"href":2409,"dataGaName":2410,"dataGaLocation":2307},"/solutions/software-compliance/","software compliance",{"title":2412,"link":2413,"items":2418},"Measurement",{"config":2414},{"icon":2415,"href":2416,"dataGaName":2417,"dataGaLocation":2307},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[2419,2423,2427],{"text":2420,"config":2421},"Visibility & Measurement",{"href":2416,"dataGaLocation":2307,"dataGaName":2422},"Visibility and Measurement",{"text":2424,"config":2425},"Value Stream Management",{"href":2426,"dataGaLocation":2307,"dataGaName":2424},"/solutions/value-stream-management/",{"text":2428,"config":2429},"Analytics & Insights",{"href":2430,"dataGaLocation":2307,"dataGaName":2431},"/solutions/analytics-and-insights/","Analytics and insights",{"title":2433,"type":2358,"items":2434},"GitLab for",[2435,2441,2447],{"text":2436,"config":2437},"Enterprise",{"icon":2438,"href":2439,"dataGaLocation":2307,"dataGaName":2440},"Building","/enterprise/","enterprise",{"text":2442,"config":2443},"Small Business",{"icon":2444,"href":2445,"dataGaLocation":2307,"dataGaName":2446},"Work","/small-business/","small business",{"text":2448,"config":2449},"Public Sector",{"icon":2450,"href":2451,"dataGaLocation":2307,"dataGaName":2452},"Organization","/solutions/public-sector/","public sector",{"text":2454,"config":2455},"Pricing",{"href":2456,"dataGaName":2457,"dataGaLocation":2307,"dataNavLevelOne":2457},"/pricing/","pricing",{"text":2459,"config":2460,"menu":2462},"Resources",{"dataNavLevelOne":2461},"resources",{"type":2358,"link":2463,"columns":2467,"feature":2561},{"text":2464,"config":2465},"View all resources",{"href":2466,"dataGaName":2461,"dataGaLocation":2307},"/resources/",[2468,2501,2528],{"title":2469,"items":2470},"Getting started",[2471,2476,2481,2486,2491,2496],{"text":2472,"config":2473},"Install",{"href":2474,"dataGaName":2475,"dataGaLocation":2307},"/install/","install",{"text":2477,"config":2478},"Quick start guides",{"href":2479,"dataGaName":2480,"dataGaLocation":2307},"/get-started/","quick setup checklists",{"text":2482,"config":2483},"Learn",{"href":2484,"dataGaLocation":2307,"dataGaName":2485},"https://university.gitlab.com/","learn",{"text":2487,"config":2488},"Product documentation",{"href":2489,"dataGaName":2490,"dataGaLocation":2307},"https://docs.gitlab.com/","product documentation",{"text":2492,"config":2493},"Best practice videos",{"href":2494,"dataGaName":2495,"dataGaLocation":2307},"/getting-started-videos/","best practice videos",{"text":2497,"config":2498},"Integrations",{"href":2499,"dataGaName":2500,"dataGaLocation":2307},"/integrations/","integrations",{"title":2502,"items":2503},"Discover",[2504,2509,2514,2519,2523],{"text":2505,"config":2506},"Customer success stories",{"href":2507,"dataGaName":2508,"dataGaLocation":2307},"/customers/","customer success stories",{"text":2510,"config":2511},"Blog",{"href":2512,"dataGaName":2513,"dataGaLocation":2307},"/blog/","blog",{"text":2515,"config":2516},"Demo Hub",{"href":2517,"dataGaName":2518,"dataGaLocation":2307},"/demo-hub/","demo hub",{"text":2520,"config":2521},"The Source",{"href":2522,"dataGaName":2513,"dataGaLocation":2307},"/the-source/",{"text":2524,"config":2525},"Remote",{"href":2526,"dataGaName":2527,"dataGaLocation":2307},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":2529,"items":2530},"Connect",[2531,2536,2541,2546,2551,2556],{"text":2532,"config":2533},"GitLab Services",{"href":2534,"dataGaName":2535,"dataGaLocation":2307},"/services/","services",{"text":2537,"config":2538},"Contribute",{"href":2539,"dataGaName":2540,"dataGaLocation":2307},"https://contributors.gitlab.com","contribute",{"text":2542,"config":2543},"Community",{"href":2544,"dataGaName":2545,"dataGaLocation":2307},"/community/","community",{"text":2547,"config":2548},"Forum",{"href":2549,"dataGaName":2550,"dataGaLocation":2307},"https://forum.gitlab.com/","forum",{"text":2552,"config":2553},"Events",{"href":2554,"dataGaName":2555,"dataGaLocation":2307},"/events/","events",{"text":2557,"config":2558},"Partners",{"href":2559,"dataGaName":2560,"dataGaLocation":2307},"/partners/","partners",{"config":2562,"title":2565,"text":2566,"link":2567},{"background":2563,"textColor":2564},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":2568,"config":2569},"Read the latest",{"href":2570,"dataGaName":2571,"dataGaLocation":2307},"/whats-new/","whats new",{"text":2573,"config":2574,"menu":2576},"Company",{"dataNavLevelOne":2575},"company",{"type":2358,"columns":2577},[2578],{"items":2579},[2580,2585,2591,2593,2598,2603,2608,2613,2618,2623],{"text":2581,"config":2582},"About",{"href":2583,"dataGaName":2584,"dataGaLocation":2307},"/company/","about",{"text":2586,"config":2587,"footerGa":2590},"Jobs",{"href":2588,"dataGaName":2589,"dataGaLocation":2307},"/jobs/","jobs",{"dataGaName":2589},{"text":2552,"config":2592},{"href":2554,"dataGaName":2555,"dataGaLocation":2307},{"text":2594,"config":2595},"Leadership",{"href":2596,"dataGaName":2597,"dataGaLocation":2307},"/company/team/e-group/","leadership",{"text":2599,"config":2600},"Handbook",{"href":2601,"dataGaName":2602,"dataGaLocation":2307},"https://handbook.gitlab.com/","handbook",{"text":2604,"config":2605},"Investor relations",{"href":2606,"dataGaName":2607,"dataGaLocation":2307},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":2609,"config":2610},"Trust Center",{"href":2611,"dataGaName":2612,"dataGaLocation":2307},"/security/","trust center",{"text":2614,"config":2615},"AI Transparency Center",{"href":2616,"dataGaName":2617,"dataGaLocation":2307},"/ai-transparency-center/","ai transparency center",{"text":2619,"config":2620},"Newsletter",{"href":2621,"dataGaName":2622,"dataGaLocation":2307},"/company/contact/#contact-forms","newsletter",{"text":2624,"config":2625},"Press",{"href":2626,"dataGaName":2627,"dataGaLocation":2307},"/press/","press",{"text":2629,"config":2630,"menu":2631},"Contact us",{"dataNavLevelOne":2575},{"type":2358,"columns":2632},[2633],{"items":2634},[2635,2640,2645],{"text":2636,"config":2637},"Talk to sales",{"href":2638,"dataGaName":2639,"dataGaLocation":2307},"/sales/","talk to sales",{"text":2641,"config":2642},"Support portal",{"href":2643,"dataGaName":2644,"dataGaLocation":2307},"https://support.gitlab.com/hc/en-us","support portal",{"text":2646,"config":2647},"Customer portal",{"href":2648,"dataGaName":2649,"dataGaLocation":2307},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":2651,"login":2652,"suggestions":2659},"Close",{"text":2653,"link":2654},"To search repositories and projects, login to",{"text":2655,"config":2656},"gitlab.com",{"href":2321,"dataGaName":2657,"dataGaLocation":2658},"search login","search",{"text":2660,"default":2661},"Suggestions",[2662,2664,2668,2670,2674,2678],{"text":2338,"config":2663},{"href":2343,"dataGaName":2338,"dataGaLocation":2658},{"text":2665,"config":2666},"Code Suggestions (AI)",{"href":2667,"dataGaName":2665,"dataGaLocation":2658},"/solutions/code-suggestions/",{"text":2374,"config":2669},{"href":2376,"dataGaName":2374,"dataGaLocation":2658},{"text":2671,"config":2672},"GitLab on AWS",{"href":2673,"dataGaName":2671,"dataGaLocation":2658},"/partners/technology-partners/aws/",{"text":2675,"config":2676},"GitLab on Google Cloud",{"href":2677,"dataGaName":2675,"dataGaLocation":2658},"/partners/technology-partners/google-cloud-platform/",{"text":2679,"config":2680},"Why GitLab?",{"href":2351,"dataGaName":2679,"dataGaLocation":2658},{"freeTrial":2682,"mobileIcon":2687,"desktopIcon":2692,"secondaryButton":2695},{"text":2683,"config":2684},"Start free trial",{"href":2685,"dataGaName":2312,"dataGaLocation":2686},"https://gitlab.com/-/trials/new/","nav",{"altText":2688,"config":2689},"Gitlab Icon",{"src":2690,"dataGaName":2691,"dataGaLocation":2686},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":2688,"config":2693},{"src":2694,"dataGaName":2691,"dataGaLocation":2686},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":2696,"config":2697},"Get Started",{"href":2698,"dataGaName":2699,"dataGaLocation":2686},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":2701,"mobileIcon":2705,"desktopIcon":2707},{"text":2702,"config":2703},"Learn more about GitLab Duo",{"href":2343,"dataGaName":2704,"dataGaLocation":2686},"gitlab duo",{"altText":2688,"config":2706},{"src":2690,"dataGaName":2691,"dataGaLocation":2686},{"altText":2688,"config":2708},{"src":2694,"dataGaName":2691,"dataGaLocation":2686},{"button":2710,"mobileIcon":2715,"desktopIcon":2717},{"text":2711,"config":2712},"/switch",{"href":2713,"dataGaName":2714,"dataGaLocation":2686},"#contact","switch",{"altText":2688,"config":2716},{"src":2690,"dataGaName":2691,"dataGaLocation":2686},{"altText":2688,"config":2718},{"src":2719,"dataGaName":2691,"dataGaLocation":2686},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":2721,"mobileIcon":2726,"desktopIcon":2728},{"text":2722,"config":2723},"Back to pricing",{"href":2456,"dataGaName":2724,"dataGaLocation":2686,"icon":2725},"back to pricing","GoBack",{"altText":2688,"config":2727},{"src":2690,"dataGaName":2691,"dataGaLocation":2686},{"altText":2688,"config":2729},{"src":2694,"dataGaName":2691,"dataGaLocation":2686},{"title":2731,"titleMobile":2732,"button":2733,"config":2738},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":2349,"config":2734},{"href":2735,"dataGaName":2736,"dataGaLocation":2737},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":2739,"disabled":2285},"release",{"data":2741},{"text":2742,"source":2743,"edit":2749,"contribute":2754,"config":2759,"items":2764,"minimal":2973},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":2744,"config":2745},"View page source",{"href":2746,"dataGaName":2747,"dataGaLocation":2748},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":2750,"config":2751},"Edit this page",{"href":2752,"dataGaName":2753,"dataGaLocation":2748},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":2755,"config":2756},"Please contribute",{"href":2757,"dataGaName":2758,"dataGaLocation":2748},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":2760,"facebook":2761,"youtube":2762,"linkedin":2763},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[2765,2812,2865,2909,2941],{"title":2454,"links":2766,"subMenu":2781},[2767,2771,2776],{"text":2768,"config":2769},"View plans",{"href":2456,"dataGaName":2770,"dataGaLocation":2748},"view plans",{"text":2772,"config":2773},"Why Premium?",{"href":2774,"dataGaName":2775,"dataGaLocation":2748},"/pricing/premium/","why premium",{"text":2777,"config":2778},"Why Ultimate?",{"href":2779,"dataGaName":2780,"dataGaLocation":2748},"/pricing/ultimate/","why ultimate",[2782],{"title":2783,"links":2784},"Contact Us",[2785,2788,2790,2792,2797,2802,2807],{"text":2786,"config":2787},"Contact sales",{"href":2638,"dataGaName":2317,"dataGaLocation":2748},{"text":2641,"config":2789},{"href":2643,"dataGaName":2644,"dataGaLocation":2748},{"text":2646,"config":2791},{"href":2648,"dataGaName":2649,"dataGaLocation":2748},{"text":2793,"config":2794},"Status",{"href":2795,"dataGaName":2796,"dataGaLocation":2748},"https://status.gitlab.com/","status",{"text":2798,"config":2799},"Terms of use",{"href":2800,"dataGaName":2801,"dataGaLocation":2748},"/terms/","terms of use",{"text":2803,"config":2804},"Privacy statement",{"href":2805,"dataGaName":2806,"dataGaLocation":2748},"/privacy/","privacy statement",{"text":2808,"config":2809},"Cookie preferences",{"dataGaName":2810,"dataGaLocation":2748,"id":2811,"isOneTrustButton":302},"cookie preferences","ot-sdk-btn",{"title":2354,"links":2813,"subMenu":2822},[2814,2818],{"text":2815,"config":2816},"DevSecOps platform",{"href":2336,"dataGaName":2817,"dataGaLocation":2748},"devsecops platform",{"text":2819,"config":2820},"AI-Assisted Development",{"href":2343,"dataGaName":2821,"dataGaLocation":2748},"ai-assisted development",[2823],{"title":2824,"links":2825},"Topics",[2826,2831,2836,2840,2845,2850,2855,2860],{"text":2827,"config":2828},"CICD",{"href":2829,"dataGaName":2830,"dataGaLocation":2748},"/topics/ci-cd/","cicd",{"text":2832,"config":2833},"GitOps",{"href":2834,"dataGaName":2835,"dataGaLocation":2748},"/topics/gitops/","gitops",{"text":2298,"config":2837},{"href":2838,"dataGaName":2839,"dataGaLocation":2748},"/topics/devops/","devops",{"text":2841,"config":2842},"Version Control",{"href":2843,"dataGaName":2844,"dataGaLocation":2748},"/topics/version-control/","version control",{"text":2846,"config":2847},"DevSecOps",{"href":2848,"dataGaName":2849,"dataGaLocation":2748},"/topics/devsecops/","devsecops",{"text":2851,"config":2852},"Cloud Native",{"href":2853,"dataGaName":2854,"dataGaLocation":2748},"/topics/cloud-native/","cloud native",{"text":2856,"config":2857},"AI for Coding",{"href":2858,"dataGaName":2859,"dataGaLocation":2748},"/topics/devops/ai-for-coding/","ai for coding",{"text":2861,"config":2862},"Agentic AI",{"href":2863,"dataGaName":2864,"dataGaLocation":2748},"/topics/agentic-ai/","agentic ai",{"title":2866,"links":2867},"Solutions",[2868,2870,2872,2877,2881,2884,2888,2891,2893,2896,2899,2904],{"text":2398,"config":2869},{"href":2393,"dataGaName":2398,"dataGaLocation":2748},{"text":2387,"config":2871},{"href":2370,"dataGaName":2371,"dataGaLocation":2748},{"text":2873,"config":2874},"Agile development",{"href":2875,"dataGaName":2876,"dataGaLocation":2748},"/solutions/agile-delivery/","agile delivery",{"text":2878,"config":2879},"SCM",{"href":2383,"dataGaName":2880,"dataGaLocation":2748},"source code management",{"text":2827,"config":2882},{"href":2376,"dataGaName":2883,"dataGaLocation":2748},"continuous integration & delivery",{"text":2885,"config":2886},"Value stream management",{"href":2426,"dataGaName":2887,"dataGaLocation":2748},"value stream management",{"text":2832,"config":2889},{"href":2890,"dataGaName":2835,"dataGaLocation":2748},"/solutions/gitops/",{"text":2436,"config":2892},{"href":2439,"dataGaName":2440,"dataGaLocation":2748},{"text":2894,"config":2895},"Small business",{"href":2445,"dataGaName":2446,"dataGaLocation":2748},{"text":2897,"config":2898},"Public sector",{"href":2451,"dataGaName":2452,"dataGaLocation":2748},{"text":2900,"config":2901},"Education",{"href":2902,"dataGaName":2903,"dataGaLocation":2748},"/solutions/education/","education",{"text":2905,"config":2906},"Financial services",{"href":2907,"dataGaName":2908,"dataGaLocation":2748},"/solutions/finance/","financial services",{"title":2459,"links":2910},[2911,2913,2915,2917,2920,2922,2925,2927,2929,2931,2933,2935,2937,2939],{"text":2472,"config":2912},{"href":2474,"dataGaName":2475,"dataGaLocation":2748},{"text":2477,"config":2914},{"href":2479,"dataGaName":2480,"dataGaLocation":2748},{"text":2482,"config":2916},{"href":2484,"dataGaName":2485,"dataGaLocation":2748},{"text":2487,"config":2918},{"href":2489,"dataGaName":2919,"dataGaLocation":2748},"docs",{"text":2510,"config":2921},{"href":2512,"dataGaName":2513,"dataGaLocation":2748},{"text":2923,"config":2924},"What's new",{"href":2570,"dataGaName":2571,"dataGaLocation":2748},{"text":2505,"config":2926},{"href":2507,"dataGaName":2508,"dataGaLocation":2748},{"text":2524,"config":2928},{"href":2526,"dataGaName":2527,"dataGaLocation":2748},{"text":2532,"config":2930},{"href":2534,"dataGaName":2535,"dataGaLocation":2748},{"text":2537,"config":2932},{"href":2539,"dataGaName":2540,"dataGaLocation":2748},{"text":2542,"config":2934},{"href":2544,"dataGaName":2545,"dataGaLocation":2748},{"text":2547,"config":2936},{"href":2549,"dataGaName":2550,"dataGaLocation":2748},{"text":2552,"config":2938},{"href":2554,"dataGaName":2555,"dataGaLocation":2748},{"text":2557,"config":2940},{"href":2559,"dataGaName":2560,"dataGaLocation":2748},{"title":2573,"links":2942},[2943,2945,2947,2949,2951,2953,2957,2962,2964,2966,2968],{"text":2581,"config":2944},{"href":2583,"dataGaName":2575,"dataGaLocation":2748},{"text":2586,"config":2946},{"href":2588,"dataGaName":2589,"dataGaLocation":2748},{"text":2594,"config":2948},{"href":2596,"dataGaName":2597,"dataGaLocation":2748},{"text":2599,"config":2950},{"href":2601,"dataGaName":2602,"dataGaLocation":2748},{"text":2604,"config":2952},{"href":2606,"dataGaName":2607,"dataGaLocation":2748},{"text":2954,"config":2955},"Sustainability",{"href":2956,"dataGaName":2954,"dataGaLocation":2748},"/sustainability/",{"text":2958,"config":2959},"Diversity, inclusion and belonging (DIB)",{"href":2960,"dataGaName":2961,"dataGaLocation":2748},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":2609,"config":2963},{"href":2611,"dataGaName":2612,"dataGaLocation":2748},{"text":2619,"config":2965},{"href":2621,"dataGaName":2622,"dataGaLocation":2748},{"text":2624,"config":2967},{"href":2626,"dataGaName":2627,"dataGaLocation":2748},{"text":2969,"config":2970},"Modern Slavery Transparency Statement",{"href":2971,"dataGaName":2972,"dataGaLocation":2748},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":2974},[2975,2978,2981],{"text":2976,"config":2977},"Terms",{"href":2800,"dataGaName":2801,"dataGaLocation":2748},{"text":2979,"config":2980},"Cookies",{"dataGaName":2810,"dataGaLocation":2748,"id":2811,"isOneTrustButton":302},{"text":2982,"config":2983},"Privacy",{"href":2805,"dataGaName":2806,"dataGaLocation":2748},[2985],{"id":2986,"title":7,"body":2284,"config":2987,"content":2989,"description":2284,"extension":2993,"meta":2994,"navigation":302,"path":2995,"seo":2996,"stem":2997,"__hash__":2998},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":2988},"BlogAuthor",{"name":7,"config":2990},{"headshot":2991,"ctfId":2992},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[3000,3009,3017],{"title":3001,"description":3002,"heroImage":3003,"category":2280,"date":3004,"authors":3005,"slug":3008,"externalUrl":2284},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[3006,3007],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":3010,"description":3011,"heroImage":3012,"category":2280,"date":3013,"authors":3014,"slug":3016,"externalUrl":2284},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[3015],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":3018,"description":3019,"heroImage":3020,"category":2280,"date":3021,"authors":3022,"slug":3024,"externalUrl":2284},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[3023],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":3026},[3027,3041,3053,3065],{"id":3028,"categories":3029,"header":3031,"text":3032,"button":3033,"image":3038},"ai-modernization",[3030],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":3034,"config":3035},"Get your AI maturity score",{"href":3036,"dataGaName":3037,"dataGaLocation":2513},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":3039},{"src":3040},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":3042,"categories":3043,"header":3045,"text":3032,"button":3046,"image":3050},"devops-modernization",[3044,2849],"product","Are you just managing tools or shipping innovation?",{"text":3047,"config":3048},"Get your DevOps maturity score",{"href":3049,"dataGaName":3037,"dataGaLocation":2513},"/assessments/devops-modernization-assessment/",{"config":3051},{"src":3052},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":3054,"categories":3055,"header":3057,"text":3032,"button":3058,"image":3062},"security-modernization",[3056],"security","Are you trading speed for security?",{"text":3059,"config":3060},"Get your security maturity score",{"href":3061,"dataGaName":3037,"dataGaLocation":2513},"/assessments/security-modernization-assessment/",{"config":3063},{"src":3064},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":3066,"paths":3067,"header":3070,"text":3071,"button":3072,"image":3077},"github-azure-migration",[3068,3069],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":3073,"config":3074},"See how GitLab compares to GitHub",{"href":3075,"dataGaName":3076,"dataGaLocation":2513},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":3078},{"src":3052},{"header":3080,"blurb":3081,"button":3082,"secondaryButton":3087},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":3083,"config":3084},"Get your free trial",{"href":3085,"dataGaName":2312,"dataGaLocation":3086},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":2786,"config":3088},{"href":2638,"dataGaName":2317,"dataGaLocation":3086},1786803758845]