I think the behavior you observe is correct, as per the docs:
To unmarshal JSON into a pointer, Unmarshal first handles the case of the JSON being the JSON literal null. In that case, Unmarshal sets the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into the value pointed at by the pointer. If the pointer is nil, Unmarshal allocates a new value for it to point to.
Remember that omitempty only applies to marshaling, not unmarshaling.
With a JSON based patch method I want to deal with these three cases:
It seems like you should implement a custom string type which handles the three cases properly via UnmarshalJSON. You can have arbitrary logic that way, really. **string isn't quite going to do what you want as per the docs above, and changing those rules now would likely break many existing programs.
Why I assumed that omitempty is also used for decoding is in fact the following lines of the documentation:
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps, slices, and pointers as necessary, with the following additional rules:
Introducing a new type with UnmarshalJSON leads me directly to the open issues in #11939.
Therefore I have now specified a suitable UnmarshalJSON method on the patch object type T, which (depending on omitempty) takes care of decoding U- *U- or **U-values.