Repository navigation
Bump required Go version to 1.27 and implement json/v2.UnmarshalerFrom and json/v2.MarshalerTo - #16
Conversation
| var update SliceUpdate[int] | ||
| err := update.UnmarshalJSON([]byte(testCase.json)) |
There was a problem hiding this comment.
Calling json.Unmarshal will just call UnmarshalJSONFrom since it has higher precedence. Calling Update.UnmarshalJSON directly is the simplest way to exercise the function.
| // MarshalJSONTo implements [json.MarshalerTo]. | ||
| func (u Update[T]) MarshalJSONTo(encoder *jsontext.Encoder) error { | ||
| if u.op == OpSet { | ||
| return json.MarshalEncode(encoder, u.value) |
There was a problem hiding this comment.
Using the json/v2 functions in MarshalJSONTo/UnmarshalJSONFrom entails a potential change of behavior when calling json.Marshal/Unmarshal, depending on T. For primitives, the behavior will be unchanged, but if for instance T is a struct that uses omitempty on an int field, that field will no longer be omitted when 0. (The struct would need to switch to omitzero to maintain its old behavior.)
However, adding new methods to a struct is not considered a breaking change, even though in this case it can change the behavior of existing code. Pragmatically, nup updates are usually used for primitives (or slices of primitives), not complex structs.
Code reviewReviewed against the PR description and Niche's Go backend conventions. Four inline comments posted; this comment covers whether the change matches what it says it does. No Jira ticket key appears in the PR title, description, or the Verified, and deliberately not flaggedRecording these so they don't get re-litigated on the next review pass:
🤖 Comment from code-review-niche Claude Code agent 🤖 |
| // UnmarshalJSON implements [jsonv1.Unmarshaler]. | ||
| func (u *Update[T]) UnmarshalJSON(data []byte) error { | ||
| if string(data) == "null" { | ||
| u.op = OpRemove |
There was a problem hiding this comment.
The Claude review pointed out that unmarshaling null into a destination update with a non-nil value would result in an update whose operation is OpRemove but which would not compare equal with Remove[T](). I've updated all four unmarshal implementations to fully overwrite the destination on the non-error paths and never overwrite (even partially) on an error path.
This is once again a small change in functionality for struct types, which will no longer be merged into, but (1) we almost never unmarshal into a non-zero value, (2) we almost never use non-primitive update fields, and (3) the old merge semantics were not my intention, so I'd call this a bug fix.
json/v2 marshal/unmarshal interfacesjson/v2.UnmarshalerFrom
json/v2.UnmarshalerFromjson/v2.UnmarshalerFrom and json/v2.MarshalerTo
DavidBrown-niche
left a comment
There was a problem hiding this comment.
Thanks for the fix.
I agree that the better fix is to just switch to requests.JSONBody, fortunately we don't seem to have too many structs that have custom marshalling/unmarshalling and nup is by far the most commonly used one that does, so this seems like a fine solution to preserve existing behavior without needing larger changes.
Documentation
Addresses this bug report
Description
This bumps the Go version to 1.27, updates dependencies, and adds
UnmarshalJSONFromandMarshalJSONTomethods to bothUpdateandSliceUpdate.This also runs
go fixto modernize the module and switches the twoUnmarshalJSONtests to be closure-driven.Testing Considerations
Tested in https://github.com/nicheinc/apply/pull/113
Versioning
Minor.