Why can a YANG union, such as an interface leafref with an enumeration fallback, validate a value differently from what its author intended?
answer
- order is the algorithm
- the first member that accepts wins
- XML is all text
- a broad member hides the rest
- a lost target falls through
basics
~20 sA union tries its member types in order and keeps the first that accepts the value, so an early broad member hides later ones, XML and JSON can pick different members, and an orphaned leafref value is retried against later members.
solid answer
~50 sRFC 7950 §9.12 validates a union value "consecutively against each member type, in the order they are specified", and the first match decides the type. Three traps follow. A broad member first, such as `string` before `enumeration { enum up; enum down; }`, means the enumeration is never matched; RFC 9907 §4.11.4 says to order members from most to least restrictive. Encodings differ: in XML every value is text, while RFC 7951 JSON carries a number-or-string distinction, so `union { string; int32; }` types 42 as a string in XML but as int32 in JSON. And a `leafref` member is revalidated when its target is deleted: in `union { interface-ref; enumeration { enum none; } }` the orphaned name fails both members and the change is refused, but with `string` as the second member the dangling name stays valid.
code
yang · 16 linesleaf egress-interface {
type union {
type if:interface-ref; // leafref, require-instance true
type enumeration {
enum none;
}
}
}
// Risky variant: a deleted interface's name stays valid
leaf egress-interface-loose {
type union {
type if:interface-ref;
type string;
}
}go deeper
Recall that a union holds a value of one of several listed types and that the list is tried from the top.
Explain first-match selection, why a broad member placed early hides narrower ones, and why XML and JSON can choose different members.
Trace what a union does when a leafref member's target is deleted, and catch catch-all members in review before they hide dangling references.
Set modelling guidance for unions across teams: when a union is the honest type, which fallbacks are allowed, and how both encodings are tested.
## How a union picks a member `union` is a YANG built-in type (RFC 7950 §9.12) whose value belongs to one of several **member types**, listed as repeated `type` substatements. Any built-in or derived type may be a member, including another union or a `leafref`. The selection rule is short and literal: when interpreting a value, the receiver validates it "consecutively against each member type, in the order they are specified" until one matches. The member that matched becomes the value's type, and its rules decide the encoding and the canonical form. Nothing picks the most specific or the best match; **order is the algorithm**. The common types module (RFC 9911, which obsoletes RFC 6991) is built on unions: `inet:ip-address` is a union of `ipv4-address` and `ipv6-address`, and, as the module's description puts it, the format of the textual representation implies the IP version. ## Ordering mistakes | Union as written | What actually happens | Fix | |---|---|---| | `string`, then `enumeration { enum up; enum down; }` | `up` matches `string`; the enumeration is never chosen | put the enumeration first | | `string`, then `int32` | XML `42` is a string, JSON `42` is an int32 | put `int32` first | | `leafref` to interface names, then `string` | a deleted interface's name silently stays valid as a string | use a narrow fallback such as an enumeration | RFC 9907 §4.11.4 states the general rule: member types SHOULD be ordered from most restrictive to least restrictive. ## XML versus JSON - In the **XML encoding**, every leaf value is character data, so the receiver can only test the text against each member in turn. - In the **JSON encoding** of RFC 7951 §6.10, the value's JSON type is part of the evidence: a JSON number cannot be a `string` member, and a quoted "1" is a string, not a number. - RFC 7951's own example is `union { type uint16; type string; }`: in XML, `<bar>13.5</bar>` is accepted as the string member, while in JSON `"bar": 13.5` is an error, because a JSON number must match a numeric member and 13.5 is not a `uint16`. - Putting numeric members before `string` makes both encodings land on the same member. ## The leafref in a union RFC 7950 warns that a `leafref` member with `require-instance` true must be revalidated when its target is deleted. Trace it with a leaf that names an egress interface or the keyword `none`: 1. The leaf is `union { type if:interface-ref; type enumeration { enum none; } }` and holds `eth1`; interface `eth1` exists, so the leafref member matches. 2. A change deletes interface `eth1`. 3. Validation retries the value: `eth1` no longer satisfies the leafref, so the next member is tried. 4. `eth1` is not `none`, so no member matches, the resulting configuration is invalid, and the change is refused. 5. Had the second member been `string`, step 4 would succeed: the leaf would quietly hold the name of an interface that no longer exists. The first version gives the operator an error to act on; the second hides a dangling reference behind a valid-looking value. ## Other union rules - **No restriction on the union itself.** A `range`, `length` or `pattern` cannot be applied to a union; restrict each member instead. - **Nothing inherited from members.** A `default` or `units` defined in a member type is not inherited by the union. - **Canonical form follows the member.** The canonical form of a union value is that of the member type that matched. - **Members are full types.** A typedef member such as `if:interface-ref` keeps all its own restrictions. ## A review checklist 1. List the member types top to bottom and, for each realistic value, name the first member that accepts it. 2. Repeat the exercise for the JSON encoding, where numbers, booleans and strings are distinct kinds of value. 3. Look for a `string` member, or any other unrestricted member, anywhere but last, and ask what it hides. 4. For every `leafref` member, ask what the value falls through to when the target disappears, and whether that fallback should accept it. ## Why it matters in operations Union surprises reach production as "the controller sent the same value and the device stored something else": a value typed as a string where a number was meant, a keyword that never matched its enumeration, or a reference that stayed valid after its target was removed. Reviewing a union means reading its members in order and asking, for each realistic value, which member catches it first in each encoding the network uses.
- Why does RFC 9907 tell YANG authors to put int32 before string in a union?In XML every value is text, so a string member listed first would match "42" before int32 is tried. In JSON a numeric value cannot match a string member, so it lands on int32. Listing int32 first makes both encodings choose the same member for the same value.
- Can a YANG leaf add a range to a typedef whose type is a union?No. RFC 7950 §9.12.1 says a union cannot be restricted. Restrict the member types instead, for example by defining a narrower typedef for the numeric member and building a new union from it.
saying these in an interview costs you the question
- A union picks the most specific member type that matches.
- The order of member types in a union does not matter.
- A trailing string member is a harmless catch-all.
- A union inherits its members' defaults and units.
- A value always resolves to the same member in XML and JSON.