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