Why is it bad practice to test for item names?

I've heard that testing for item names and lores in Minecraft JE is bad practice. For example, the following commands are not good:

execute if entity @e[nbt={Inventory:[{tag:{display:{Name:'[{"text":"Test Thing"}]'}}}]}]
clear @p minecraft:diamond{display:{Name:'{"text":"Shiny…"}'}} 0
clear @p minecraft:netherite_sword{display:{Lore:['{"text":"A Mysterious Sword"}']}} 0

Why are these commands considered bad practice? Are they prone to failure and may not work? And what can I do to fix them?

Note that this only applies to reading item names, not setting them. The following are not considered bad practice and in fact are the only ways to accomplish these tasks:

give @p minecraft:diamond{display:{Name:'{"text":"Shiny…"}'}}
replaceitem @p weapon.mainhand minecraft:netherite_sword{display:{Lore:['{"text":"A Mysterious Sword"}']}}

Solution 1:

The reason testing for an item name is bad practice is due to the way that the NBT processor checks strings.

Remember that in NBT, the Name tag and others are just NBT strings containing JSON inside of them:

{
  display: {
    Name: '[{"text":"Test"}]'
  }
}

When you test for the Name tag, you are doing a plain text check on the raw JSON source of the item, not on the rendered output. This means that while two different item names may look the same when rendered, they may have been created with slightly different JSON sources and will therefore be considered non-matching.

For example, {"text":"test"} and [{"text":"test"}]. They render the same, but are considered different to the NBT processor because of the extra square brackets.

Another example, {"text":"test","bold":true} and {"bold":true,"text":"test"}. They render the same, but are considered different because of the order of the components.

Another example, {"text": "test"} and {"text":"test"}. Look the same? They're different because there is a space after the : in the first one. Every character counts! A mishap by just one character will make it different!

Even if you try to make sure all your JSON is consistent, it is still unreliable, because Minecraft has a nasty habit of converting your JSON texts into a format it considers standard. And we haven't been able to pinpoint what triggers this behaviour. That's why testing for JSON texts like name and stuff are so unreliable and should never be used.

Instead of testing for an item name, use a custom item tag instead. Unlike elsewhere, the tag area of items allows for custom NBT tags that the game does not use by default. So change the command to get this item into something like:

give @p minecraft:diamond{display:{Name:'{"text":"Shiny…"}'},showoffDiamond:1b}
replaceitem @p weapon.mainhand minecraft:netherite_sword{display:{Lore:['{"text":"A Mysterious Sword"}']},mysterySword:1b}

and test for that instead:

execute if entity @e[nbt={Inventory:[{tag:{testItem:1b}}]}]
clear @p minecraft:diamond{showoffDiamond:1b} 0
clear @p minecraft:netherite_sword{mysterySword:1b} 0