github.com/Jeffail/benthos/v3@v3.65.0/website/docs/guides/bloblang/about.md (about) 1 --- 2 title: Bloblang 3 sidebar_label: About 4 description: The Benthos native mapping language 5 --- 6 7 Bloblang, or blobl for short, is a language designed for mapping data of a wide variety of forms. It's a safe, fast, and powerful way to perform document mapping within Benthos. It also has a [Go API for writing your own functions and methods][plugin-api] as plugins. 8 9 Bloblang is available as a [processor][blobl.proc] and it's also possible to use blobl queries in [function interpolations][blobl.interp]. 10 11 You can also execute Bloblang mappings on the command-line with the `blobl` subcommand: 12 13 ```shell 14 $ cat data.jsonl | benthos blobl 'foo.(bar | baz).buz' 15 ``` 16 17 This document outlines the core features of the Bloblang language, but if you're totally new to Bloblang then it's worth following [the walkthrough first][blobl.walkthrough]. 18 19 ## Assignment 20 21 A Bloblang mapping expresses how to create a new document by extracting data from an existing input document. Assignments consist of a dot separated path segments on the left-hand side describing a field to be created within the new document, and a right-hand side query describing what the content of the new field should be. 22 23 The keyword `root` on the left-hand side refers to the root of the new document, the keyword `this` on the right-hand side refers to the current context of the query, which is the read-only input document when querying from the root of a mapping: 24 25 ```coffee 26 root.id = this.thing.id 27 root.type = "yo" 28 29 # Both `root` and `this` are optional, and will be inferred in their absence. 30 content = thing.doc.message 31 32 # In: {"thing":{"id":"wat1","doc":{"title":"wut","message":"hello world"}}} 33 # Out: {"content":"hello world","id":"wat1","type":"yo"} 34 ``` 35 36 Since the document being created starts off empty it is sometimes useful to begin a mapping by copying the entire contents of the input document, which can be expressed by assigning `this` to `root`. 37 38 ```coffee 39 root = this 40 root.foo = "added value" 41 42 # In: {"id":"wat1","message":"hello world"} 43 # Out: {"id":"wat1","message":"hello world","foo":"added value"} 44 ``` 45 46 If the new document `root` is never assigned to or otherwise mutated then the original document remains unchanged. 47 48 ### Special Characters in Paths 49 50 Quotes can be used to describe sections of a field path that contain whitespace, dots or other special characters: 51 52 ```coffee 53 # Use quotes around a path segment in order to include whitespace or dots within 54 # the path 55 root."foo.bar".baz = this."buz bev".fub 56 57 # In: {"buz bev":{"fub":"hello world"}} 58 # Out: {"foo.bar":{"baz":"hello world"}} 59 ``` 60 61 ### Non-structured Data 62 63 Bloblang is able to map data that is unstructured, whether it's a log line or a binary blob, by referencing it with the [`content` function][blobl.functions.content], which returns the raw bytes of the input document: 64 65 ```coffee 66 # Parse a base64 encoded JSON document 67 root = content().decode("base64").parse_json() 68 69 # In: eyJmb28iOiJiYXIifQ== 70 # Out: {"foo":"bar"} 71 ``` 72 73 And your newly mapped document can also be unstructured, simply assign a value type to the `root` of your document: 74 75 ```coffee 76 root = this.foo 77 78 # In: {"foo":"hello world"} 79 # Out: hello world 80 ``` 81 82 And the resulting message payload will be the raw value you've assigned. 83 84 ### Deleting 85 86 It's possible to selectively delete fields from an object by assigning the function `deleted()` to the field path: 87 88 ```coffee 89 root = this 90 root.bar = deleted() 91 92 # In: {"id":"wat1","message":"hello world","bar":"remove me"} 93 # Out: {"id":"wat1","message":"hello world"} 94 ``` 95 96 ### Variables 97 98 Another type of assignment is a `let` statement, which creates a variable that can be referenced elsewhere within a mapping. Variables are discarded at the end of the mapping and are mostly useful for query reuse. Variables are referenced within queries with `$`: 99 100 ```coffee 101 # Set a temporary variable 102 let foo = "yo" 103 104 root.new_doc.type = $foo 105 ``` 106 107 ### Metadata 108 109 Benthos messages contain metadata that is separate from the main payload, in Bloblang you can modify the metadata of the resulting message with the `meta` assignment keyword, and you can query the metadata of the input message with the [`meta` function][blobl.functions.meta]: 110 111 ```coffee 112 # Delete all existing metadata 113 meta = deleted() 114 115 # Set a metadata value 116 meta bar = "hello world" 117 118 # Reference a metadata value from the input message 119 root.new_doc.bar = meta("kafka_topic") 120 ``` 121 122 The [`meta` function][blobl.functions.meta] returns the read-only metadata of the input message, so it will not reflect changes you've made within the same mapping. This is why it's possible to begin a mapping by removing all old metadata `meta = deleted()` and still be able to query the original metadata. 123 124 If you wish to set a metadata value and then refer back to it later then first set it [as a variable][blobl.variables]. 125 126 ## Coalesce 127 128 The pipe operator (`|`) used within brackets allows you to coalesce multiple candidates for a path segment. The first field that exists and has a non-null value will be selected: 129 130 ```coffee 131 root.new_doc.type = this.thing.(article | comment | this).type 132 133 # In: {"thing":{"article":{"type":"foo"}}} 134 # Out: {"new_doc":{"type":"foo"}} 135 136 # In: {"thing":{"comment":{"type":"bar"}}} 137 # Out: {"new_doc":{"type":"bar"}} 138 139 # In: {"thing":{"type":"baz"}} 140 # Out: {"new_doc":{"type":"baz"}} 141 ``` 142 143 Opening brackets on a field begins a query where the context of `this` changes to value of the path it is opened upon, therefore in the above example `this` within the brackets refers to the contents of `this.thing`. 144 145 ## Literals 146 147 Bloblang supports number, boolean, string, null, array and object literals: 148 149 ```coffee 150 root = [ 151 7, false, "string", null, { 152 "first": 11, 153 "second": {"foo":"bar"}, 154 "third": """multiple 155 lines on this 156 string""" 157 } 158 ] 159 160 # In: {} 161 # Out: [7,false,"string",null,{"first":11,"second":{"foo":"bar"},"third":"multiple\nlines on this\nstring"}] 162 ``` 163 164 The values within literal arrays and objects can be dynamic query expressions, as well as the keys of object literals. 165 166 ## Comments 167 168 You might've already spotted, comments are started with a hash (`#`) and end with a line break: 169 170 ```coffee 171 root = this.some.value # And now this is a comment 172 ``` 173 174 ## Boolean Logic and Arithmetic 175 176 Bloblang supports a range of boolean operators `!`, `>`, `>=`, `==`, `<`, `<=`, `&&`, `||` and mathematical operators `+`, `-`, `*`, `/`, `%`: 177 178 ```coffee 179 root.is_big = this.number > 100 180 root.multiplied = this.number * 7 181 182 # In: {"number":50} 183 # Out: {"is_big":false,"multiplied":350} 184 185 # In: {"number":150} 186 # Out: {"is_big":true,"multiplied":1050} 187 ``` 188 189 For more information about these operators and how they work check out [the arithmetic page][blobl.arithmetic]. 190 191 ## Conditional Mapping 192 193 Use `if` expressions to perform maps conditionally: 194 195 ```coffee 196 root = this 197 root.sorted_foo = if this.foo.type() == "array" { this.foo.sort() } 198 199 # In: {"foo":"foobar"} 200 # Out: {"foo":"foobar"} 201 202 # In: {"foo":["foo","bar"]} 203 # Out: {"foo":["foo","bar"],"sorted_foo":["bar","foo"]} 204 ``` 205 206 And add as many `if else` queries as you like, followed by an optional final fallback `else`: 207 208 ```coffee 209 root.sound = if this.type == "cat" { 210 this.cat.meow 211 } else if this.type == "dog" { 212 this.dog.woof.uppercase() 213 } else { 214 "sweet sweet silence" 215 } 216 217 # In: {"type":"cat","cat":{"meow":"meeeeooooow!"}} 218 # Out: {"sound":"meeeeooooow!"} 219 220 # In: {"type":"dog","dog":{"woof":"guurrrr woof woof!"}} 221 # Out: {"sound":"GUURRRR WOOF WOOF!"} 222 223 # In: {"type":"caterpillar","caterpillar":{"name":"oleg"}} 224 # Out: {"sound":"sweet sweet silence"} 225 ``` 226 227 ## Pattern Matching 228 229 A `match` expression allows you to perform conditional mappings on a value, each case should be either a boolean expression, a literal value to compare against the target value, or an underscore (`_`) which captures values that have not matched a prior case: 230 231 ```coffee 232 root.new_doc = match this.doc { 233 this.type == "article" => this.article 234 this.type == "comment" => this.comment 235 _ => this 236 } 237 238 # In: {"doc":{"type":"article","article":{"id":"foo","content":"qux"}}} 239 # Out: {"new_doc":{"id":"foo","content":"qux"}} 240 241 # In: {"doc":{"type":"comment","comment":{"id":"bar","content":"quz"}}} 242 # Out: {"new_doc":{"id":"bar","content":"quz"}} 243 244 # In: {"doc":{"type":"neither","content":"some other stuff unchanged"}} 245 # Out: {"new_doc":{"type":"neither","content":"some other stuff unchanged"}} 246 ``` 247 248 Within a match block the context of `this` changes to the pattern matched expression, therefore `this` within the match expression above refers to `this.doc`. 249 250 Match cases can specify a literal value for simple comparison: 251 252 ```coffee 253 root = this 254 root.type = match this.type { "doc" => "document", "art" => "article", _ => this } 255 256 # In: {"type":"doc","foo":"bar"} 257 # Out: {"type":"document","foo":"bar"} 258 ``` 259 260 The match expression can also be left unset which means the context remains unchanged, and the catch-all case can also be omitted: 261 262 ```coffee 263 root.new_doc = match { 264 this.doc.type == "article" => this.doc.article 265 this.doc.type == "comment" => this.doc.comment 266 } 267 268 # In: {"doc":{"type":"neither","content":"some other stuff unchanged"}} 269 # Out: {"doc":{"type":"neither","content":"some other stuff unchanged"}} 270 ``` 271 272 If no case matches then the mapping is skipped entirely, hence we would end up with the original document in this case. 273 274 ## Functions 275 276 Functions can be placed anywhere and allow you to extract information from your environment, generate values, or access data from the underlying message being mapped: 277 278 ```coffee 279 root.doc.id = uuid_v4() 280 root.doc.received_at = now() 281 root.doc.host = hostname() 282 ``` 283 284 Functions support both named and nameless style arguments: 285 286 ```coffee 287 root.values_one = range(start: 0, stop: this.max, step: 2) 288 root.values_two = range(0, this.max, 2) 289 ``` 290 291 You can find a full list of functions and their parameters in [the functions page][blobl.functions]. 292 293 ## Methods 294 295 Methods are similar to functions but enact upon a target value, these provide most of the power in Bloblang as they allow you to augment query values and can be added to any expression (including other methods): 296 297 ```coffee 298 root.doc.id = this.thing.id.string().catch(uuid_v4()) 299 root.doc.reduced_nums = this.thing.nums.map_each(num -> if num < 10 { 300 deleted() 301 } else { 302 num - 10 303 }) 304 root.has_good_taste = ["pikachu","mewtwo","magmar"].contains(this.user.fav_pokemon) 305 ``` 306 307 Methods also support both named and nameless style arguments: 308 309 ```coffee 310 root.foo_one = this.(bar | baz).trim().replace(old: "dog", new: "cat") 311 root.foo_two = this.(bar | baz).trim().replace("dog", "cat") 312 ``` 313 314 You can find a full list of methods and their parameters in [the methods page][blobl.methods]. 315 316 ## Maps 317 318 Defining named maps allows you to reuse common mappings on values with the [`apply` method][blobl.methods.apply]: 319 320 ```coffee 321 map things { 322 root.first = this.thing_one 323 root.second = this.thing_two 324 } 325 326 root.foo = this.value_one.apply("things") 327 root.bar = this.value_two.apply("things") 328 329 # In: {"value_one":{"thing_one":"hey","thing_two":"yo"},"value_two":{"thing_one":"sup","thing_two":"waddup"}} 330 # Out: {"foo":{"first":"hey","second":"yo"},"bar":{"first":"sup","second":"waddup"}} 331 ``` 332 333 Within a map the keyword `root` refers to a newly created document that will replace the target of the map, and `this` refers to the original value of the target. The argument of `apply` is a string, which allows you to dynamically resolve the mapping to apply. 334 335 ## Import Maps 336 337 It's possible to import maps defined in a file with an `import` statement: 338 339 ```coffee 340 import "./common_maps.blobl" 341 342 root.foo = this.value_one.apply("things") 343 root.bar = this.value_two.apply("things") 344 ``` 345 346 Imports from a Bloblang mapping within a Benthos config are relative to the process running the config. Imports from an imported file are relative to the file that is importing it. 347 348 ## Filtering 349 350 By assigning the root of a mapped document to the `deleted()` function you can delete a message entirely: 351 352 ```coffee 353 # Filter all messages that have fewer than 10 URLs. 354 root = if this.doc.urls.length() < 10 { deleted() } 355 ``` 356 357 ## Error Handling 358 359 Functions and methods can fail under certain circumstances, such as when they receive types they aren't able to act upon. These failures, when not caught, will cause the entire mapping to fail. However, the [method `catch`][blobl.methods.catch] can be used in order to return a value when a failure occurs instead: 360 361 ```coffee 362 # Map an empty array to `foo` if the field `bar` is not a string. 363 root.foo = this.bar.split(",").catch([]) 364 ``` 365 366 Since `catch` is a method it can also be attached to bracketed map expressions: 367 368 ```coffee 369 # Map `false` if any of the operations in this boolean query fail. 370 root.thing = ( this.foo > this.bar && this.baz.contains("wut") ).catch(false) 371 ``` 372 373 And one of the more powerful features of Bloblang is that a single `catch` method at the end of a chain of methods can recover errors from any method in the chain: 374 375 ```coffee 376 # Catch errors caused by: 377 # - foo not existing 378 # - foo not being a string 379 # - an element from split foo not being a valid JSON string 380 root.things = this.foo.split(",").map_each( ele -> ele.parse_json() ).catch([]) 381 382 # Specifically catch a JSON parse error 383 root.things = this.foo.split(",").map_each( ele -> ele.parse_json().catch({}) ) 384 ``` 385 386 However, the `catch` method only acts on errors, sometimes it's also useful to set a fall back value when a query returns `null` in which case the [method `or`][blobl.methods.or] can be used the same way: 387 388 ```coffee 389 # Map "default" if either the element index 5 does not exist, or the underlying 390 # element is `null`. 391 root.foo = this.bar.index(5).or("default") 392 ``` 393 394 ## Unit Testing 395 396 It's possible to execute unit tests for your Bloblang mappings using the standard Benthos unit test capabilities outlined [in this document][configuration.unit_testing]. 397 398 ## Trouble Shooting 399 400 1. I'm seeing `unable to reference message as structured (with 'this')` when I try to run mappings with `benthos blobl`. 401 402 That particular error message means the mapping is failing to parse what's being fed in as a JSON document. Make sure that the data you are feeding in is valid JSON, and also that the documents *do not* contain line breaks as `benthos blobl` will parse each line individually. 403 404 Why? That's a good question. Bloblang supports non-JSON formats too, so it can't delimit documents with a streaming JSON parser like tools such as `jq`, so instead it uses line breaks to determine the boundaries of each message. 405 406 [blobl.arithmetic]: /docs/guides/bloblang/arithmetic 407 [blobl.walkthrough]: /docs/guides/bloblang/walkthrough 408 [blobl.variables]: #variables 409 [blobl.proc]: /docs/components/processors/bloblang 410 [blobl.interp]: /docs/configuration/interpolation#bloblang-queries 411 [blobl.functions]: /docs/guides/bloblang/functions 412 [blobl.functions.meta]: /docs/guides/bloblang/functions#meta 413 [blobl.functions.content]: /docs/guides/bloblang/functions#content 414 [blobl.methods]: /docs/guides/bloblang/methods 415 [blobl.methods.apply]: /docs/guides/bloblang/methods#apply 416 [blobl.methods.catch]: /docs/guides/bloblang/methods#catch 417 [blobl.methods.or]: /docs/guides/bloblang/methods#or 418 [plugin-api]: https://pkg.go.dev/github.com/Jeffail/benthos/v3/public/bloblang 419 [configuration.unit_testing]: /docs/configuration/unit_testing