Skip to content

Commit 130f840

Browse files
committed
docs: rewrite wildcards section for clarity
- Replace abstract color property examples with IoT-relevant examples (Sensors, Temperature, Pressure) - Restructure into clear subsections: copy all, flatten, restructure, placement rules, multi-input, override, multiple rules, contextualization - Simplify specialization/rank explanation to practical 'specific rules override wildcards' - Remove the 'failed then corrected' teaching pattern (confusing) - Reduce from ~270 lines to ~125 lines while preserving all key concepts
1 parent 132c67c commit 130f840

1 file changed

Lines changed: 59 additions & 203 deletions

File tree

articles/iot-operations/connect-to-cloud/concept-dataflow-graphs-expressions.md

Lines changed: 59 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -337,275 +337,131 @@ The primary function of escaping in a dot-notated path is to accommodate the use
337337

338338
## Wildcards
339339

340-
In many scenarios, the output record closely resembles the input record, with only minor modifications required. When you deal with records that contain numerous fields, manually specifying mappings for each field can become tedious. Wildcards simplify this process by allowing for generalized mappings that can automatically apply to multiple fields.
340+
Use a wildcard (`*`) in input and output paths to match multiple fields at once. This is useful when the output closely resembles the input, or when you need to apply the same transformation across many fields without listing each one.
341341

342-
Let's consider a basic scenario to understand the use of asterisks in mappings:
342+
### Copy all fields
343+
344+
To pass every field through unchanged:
343345

344346
| Input | Output |
345347
|-------|--------|
346348
| `*` | `*` |
347349

348-
This configuration shows a basic mapping where every field in the input is directly mapped to the same field in the output without any changes. The asterisk (`*`) serves as a wildcard that matches any field in the input record.
350+
The `*` matches each field path in the input and places it at the same path in the output. The portion of the path that `*` matches is called the **captured segment**. In the output, the captured segment replaces the `*`.
349351

350-
Here's how the asterisk (`*`) operates in this context:
352+
### Flatten nested fields
351353

352-
* **Pattern matching**: The asterisk can match a single segment or multiple segments of a path. It serves as a placeholder for any segments in the path.
353-
* **Field matching**: During the mapping process, the algorithm evaluates each field in the input record against the pattern specified in the `inputs`. The asterisk in the previous example matches all possible paths, effectively fitting every individual field in the input.
354-
* **Captured segment**: The portion of the path that the asterisk matches is referred to as the `captured segment`.
355-
* **Output mapping**: In the output configuration, the `captured segment` is placed where the asterisk appears. This means that the structure of the input is preserved in the output, with the `captured segment` filling the placeholder provided by the asterisk.
354+
To move fields out of a nested object to the root level, put the prefix in the input and `*` in the output:
356355

357-
Another example illustrates how wildcards can be used to match subsections and move them together. This example effectively flattens nested structures within a JSON object.
356+
| Input | Output |
357+
|-------|--------|
358+
| `Sensors.*` | `*` |
359+
| `Metadata.*` | `*` |
358360

359-
Original JSON:
361+
Given this input:
360362

361363
```json
362364
{
363-
"ColorProperties": {
364-
"Hue": "blue",
365-
"Saturation": "90%",
366-
"Brightness": "50%",
367-
"Opacity": "0.8"
368-
},
369-
"TextureProperties": {
370-
"type": "fabric",
371-
"SurfaceFeel": "soft",
372-
"SurfaceAppearance": "matte",
373-
"Pattern": "knitted"
374-
}
365+
"Sensors": { "Temperature": 72.5, "Pressure": 14.7 },
366+
"Metadata": { "LineId": "Line-3", "Shift": "A" }
375367
}
376368
```
377369

378-
Mapping configuration that uses wildcards:
379-
380-
| Input | Output |
381-
|-------|--------|
382-
| `ColorProperties.*` | `*` |
383-
| `TextureProperties.*` | `*` |
384-
385-
Resulting JSON:
370+
The output flattens both objects:
386371

387372
```json
388373
{
389-
"Hue": "blue",
390-
"Saturation": "90%",
391-
"Brightness": "50%",
392-
"Opacity": "0.8",
393-
"type": "fabric",
394-
"SurfaceFeel": "soft",
395-
"SurfaceAppearance": "matte",
396-
"Pattern": "knitted"
374+
"Temperature": 72.5,
375+
"Pressure": 14.7,
376+
"LineId": "Line-3",
377+
"Shift": "A"
397378
}
398379
```
399380

400-
### Wildcard placement
381+
### Restructure fields
401382

402-
When you place a wildcard, you must follow these rules:
383+
To move fields under a new parent, put `*` in the input and add a prefix in the output:
403384

404-
* **Single asterisk per data reference:** Only one asterisk (`*`) is allowed within a single data reference.
405-
* **Full segment matching:** The asterisk must always match an entire segment of the path. It can't be used to match only a part of a segment, such as `path1.partial*.path3`.
406-
* **Positioning:** The asterisk can be positioned in various parts of a data reference:
407-
* **At the beginning:** `*.path2.path3` - Here, the asterisk matches any segment that leads up to `path2.path3`.
408-
* **In the middle:** `path1.*.path3` - In this configuration, the asterisk matches any segment between `path1` and `path3`.
409-
* **At the end:** `path1.path2.*` - The asterisk at the end matches any segment that follows after `path1.path2`.
410-
* The path containing the asterisk must be enclosed in single quotation marks (`'`).
385+
| Input | Output |
386+
|-------|--------|
387+
| `*` | `Telemetry.*` |
411388

412-
### Multi-input wildcards
389+
This wraps all top-level fields inside a `Telemetry` object.
413390

414-
Original JSON:
391+
### Wildcard placement rules
415392

416-
```json
417-
{
418-
"Saturation": {
419-
"Max": 0.42,
420-
"Min": 0.67,
421-
},
422-
"Brightness": {
423-
"Max": 0.78,
424-
"Min": 0.93,
425-
},
426-
"Opacity": {
427-
"Max": 0.88,
428-
"Min": 0.91,
429-
}
430-
}
431-
```
393+
- Only **one** `*` is allowed per input or output path.
394+
- The `*` must match a **complete segment** (not a partial segment like `Sensor*`).
395+
- The `*` can appear at the beginning (`*.Value`), middle (`Sensors.*.Reading`), or end (`Sensors.*`) of a path.
432396

433-
Mapping configuration that uses wildcards:
397+
### Multi-input wildcards
398+
399+
When a rule has multiple inputs with wildcards, the `*` must capture the **same segment** across all inputs. The runtime resolves the `*` from the first input, then looks for matching paths in the other inputs.
400+
401+
For example, to average the max and min readings for each sensor:
434402

435403
| Input | Output | Expression |
436404
|-------|--------|------------|
437-
| `*.Max` ($1)<br>`*.Min` ($2) | `ColorProperties.*` | `($1 + $2) / 2` |
405+
| `*.Max` ($1)<br>`*.Min` ($2) | `Averaged.*` | `($1 + $2) / 2` |
438406

439-
Resulting JSON:
407+
Given this input:
440408

441409
```json
442410
{
443-
"ColorProperties" : {
444-
"Saturation": 0.54,
445-
"Brightness": 0.85,
446-
"Opacity": 0.89
447-
}
411+
"Temperature": { "Max": 85.3, "Min": 62.1 },
412+
"Pressure": { "Max": 15.2, "Min": 14.1 }
448413
}
449414
```
450415

451-
If you use multi-input wildcards, the asterisk (`*`) must consistently represent the same `Captured Segment` across every input. For example, when `*` captures `Saturation` in the pattern `*.Max`, the mapping algorithm expects the corresponding `Saturation.Min` to match with the pattern `*.Min`. Here, `*` is substituted by the `Captured Segment` from the first input, guiding the matching for subsequent inputs.
452-
453-
Consider this detailed example:
454-
455-
Original JSON:
416+
The `*` captures `Temperature` first, so the rule looks for both `Temperature.Max` and `Temperature.Min`. Then it captures `Pressure` and looks for `Pressure.Max` and `Pressure.Min`. The output is:
456417

457418
```json
458419
{
459-
"Saturation": {
460-
"Max": 0.42,
461-
"Min": 0.67,
462-
"Mid": {
463-
"Avg": 0.51,
464-
"Mean": 0.56
465-
}
466-
},
467-
"Brightness": {
468-
"Max": 0.78,
469-
"Min": 0.93,
470-
"Mid": {
471-
"Avg": 0.81,
472-
"Mean": 0.82
473-
}
474-
},
475-
"Opacity": {
476-
"Max": 0.88,
477-
"Min": 0.91,
478-
"Mid": {
479-
"Avg": 0.89,
480-
"Mean": 0.89
481-
}
482-
}
420+
"Averaged": { "Temperature": 73.7, "Pressure": 14.65 }
483421
}
484422
```
485423

486-
Initial mapping configuration that uses wildcards:
487-
488-
| Input | Output | Expression |
489-
|-------|--------|------------|
490-
| `*.Max` ($1)<br>`*.Min` ($2)<br>`*.Avg` ($3)<br>`*.Mean` ($4) | `ColorProperties.*` | `($1, $2, $3, $4)` |
491-
492-
This initial mapping tries to build an array (for example, for `Opacity`: `[0.88, 0.91, 0.89, 0.89]`). This configuration fails because:
493-
494-
* The first input `*.Max` captures a segment like `Saturation`.
495-
* The mapping expects the subsequent inputs to be present at the same level:
496-
* `Saturation.Max`
497-
* `Saturation.Min`
498-
* `Saturation.Avg`
499-
* `Saturation.Mean`
500-
501-
Because `Avg` and `Mean` are nested within `Mid`, the asterisk in the initial mapping doesn't correctly capture these paths.
502-
503-
Corrected mapping configuration:
504-
505-
| Input | Output | Expression |
506-
|-------|--------|------------|
507-
| `*.Max` ($1)<br>`*.Min` ($2)<br>`*.Mid.Avg` ($3)<br>`*.Mid.Mean` ($4) | `ColorProperties.*` | `($1, $2, $3, $4)` |
508-
509-
This revised mapping accurately captures the necessary fields. It correctly specifies the paths to include the nested `Mid` object, which ensures that the asterisks work effectively across different levels of the JSON structure.
424+
If any input can't resolve for a captured segment (for example, `*.Mid.Avg` when the field is nested differently), that segment is skipped. Make sure the paths in all inputs reflect the actual structure of the data.
510425

511-
### Specialization and second rules
426+
### Override a wildcard for specific fields
512427

513-
When you use the previous example from multi-input wildcards, consider the following mappings that generate two derived values for each property:
428+
You can combine a wildcard rule with specific rules. Specific rules take precedence when they have a **lower coverage** (fewer segments matched by `*`). This is called **specialization**.
514429

515430
| Input | Output | Expression |
516431
|-------|--------|------------|
517-
| `*.Max` ($1)<br>`*.Min` ($2) | `ColorProperties.*.Avg` | `($1 + $2) / 2` |
518-
| `*.Max` ($1)<br>`*.Min` ($2) | `ColorProperties.*.Diff` | `$1 - $2` |
519-
520-
This mapping is intended to create two separate calculations (`Avg` and `Diff`) for each property under `ColorProperties`. This example shows the result:
521-
522-
```json
523-
{
524-
"ColorProperties": {
525-
"Saturation": {
526-
"Avg": 0.54,
527-
"Diff": 0.25
528-
},
529-
"Brightness": {
530-
"Avg": 0.85,
531-
"Diff": 0.15
532-
},
533-
"Opacity": {
534-
"Avg": 0.89,
535-
"Diff": 0.03
536-
}
537-
}
538-
}
539-
```
432+
| `*.Max` ($1)<br>`*.Min` ($2) | `Averaged.*` | `($1 + $2) / 2` |
433+
| `Pressure.Max` ($1)<br>`Pressure.Min` ($2) | `Averaged.PressureAdj` | `($1 + $2 + 1.0) / 2` |
540434

541-
Here, the second mapping definition on the same inputs acts as a *second rule* for mapping.
435+
The first rule applies to all fields. The second rule overrides it for `Pressure` only, because `Pressure.Max` is more specific than `*.Max` (coverage 0 vs. coverage 1).
542436

543-
Now, consider a scenario where a specific field needs a different calculation:
437+
To exclude a field entirely, use an empty output:
544438

545-
| Input | Output | Expression |
546-
|-------|--------|------------|
547-
| `*.Max` ($1)<br>`*.Min` ($2) | `ColorProperties.*` | `($1 + $2) / 2` |
548-
| `Opacity.Max` ($1)<br>`Opacity.Min` ($2) | `ColorProperties.OpacityAdjusted` | `($1 + $2 + 1.32) / 2` |
439+
| Input | Output |
440+
|-------|--------|
441+
| `Pressure.Max`, `Pressure.Min` | *(empty)* |
549442

550-
In this case, the `Opacity` field has a unique calculation. Two options to handle this overlapping scenario are:
443+
An empty output drops the field from the result. This overrides any wildcard rule that would otherwise include it.
551444

552-
- Include both mappings for `Opacity`. Because the output fields are different in this example, they wouldn't override each other.
553-
- Use the more specific rule for `Opacity` and remove the more generic one.
445+
### Multiple rules on the same inputs
554446

555-
Consider a special case for the same fields to help decide the right action:
447+
If two rules have the same or higher coverage, both apply. This lets you compute multiple derived values from the same inputs:
556448

557449
| Input | Output | Expression |
558450
|-------|--------|------------|
559-
| `*.Max` ($1)<br>`*.Min` ($2) | `ColorProperties.*` | `($1 + $2) / 2` |
560-
| `Opacity.Max`, `Opacity.Min` | *(empty)* | |
561-
562-
An empty output field in the second definition implies not writing the fields in the output record (effectively removing `Opacity`). This setup is more of a `Specialization` than a `Second Rule`.
451+
| `*.Max` ($1)<br>`*.Min` ($2) | `Stats.*.Avg` | `($1 + $2) / 2` |
452+
| `*.Max` ($1)<br>`*.Min` ($2) | `Stats.*.Range` | `$1 - $2` |
563453

564-
Resolution of overlapping mappings by data flows:
565-
566-
* The evaluation progresses from the top rule in the mapping definition.
567-
* If a new mapping resolves to the same fields as a previous rule, the following conditions apply:
568-
* A `Rank` is calculated for each resolved input based on the number of segments the wildcard captures. For instance, if the `Captured Segments` are `Properties.Opacity`, the `Rank` is 2. If it's only `Opacity`, the `Rank` is 1. A mapping without wildcards has a `Rank` of 0.
569-
* If the `Rank` of the latter rule is equal to or higher than the previous rule, a data flow treats it as a `Second Rule`.
570-
* Otherwise, the data flow treats the configuration as a `Specialization`.
571-
572-
For example, the mapping that directs `Opacity.Max` and `Opacity.Min` to an empty output has a `Rank` of 0. Because the second rule has a lower `Rank` than the previous one, it's considered a specialization and overrides the previous rule, which would calculate a value for `Opacity`.
454+
Both rules execute for each captured segment, producing two output fields per sensor.
573455

574456
### Wildcards in contextualization datasets
575457

576-
Contextualization datasets can be used with wildcards. Consider a dataset named `position` that contains the following record:
577-
578-
```json
579-
{
580-
"Position": "Analyst",
581-
"BaseSalary": 70000,
582-
"WorkingHours": "Regular"
583-
}
584-
```
585-
586-
In an earlier example, we used a specific field from this dataset:
587-
588-
| Input | Output |
589-
|-------|--------|
590-
| `$context(position).BaseSalary` | `Employment.BaseSalary` |
591-
592-
This mapping copies `BaseSalary` from the context dataset directly into the `Employment` section of the output record. If you want to automate the process and include all fields from the `position` dataset into the `Employment` section, you can use wildcards:
458+
You can use wildcards with `$context` references to copy all fields from a dataset:
593459

594460
| Input | Output |
595461
|-------|--------|
596-
| `$context(position).*` | `Employment.*` |
462+
| `$context(assetMeta).*` | `Asset.*` |
597463

598-
This configuration allows for a dynamic mapping where every field within the `position` dataset is copied into the `Employment` section of the output record:
599-
600-
```json
601-
{
602-
"Employment": {
603-
"Position": "Analyst",
604-
"BaseSalary": 70000,
605-
"WorkingHours": "Regular"
606-
}
607-
}
608-
```
464+
This copies every field from the `assetMeta` dataset into the `Asset` section of the output.
609465

610466
## Contextualization datasets
611467

0 commit comments

Comments
 (0)