Skip to content

fix: Go SDK metrics unmarshal error with OptionalNullable[time.Time]#867

Merged
alexluong merged 3 commits intomainfrom
fix/go-sdk-metrics-unmarshal
Apr 23, 2026
Merged

fix: Go SDK metrics unmarshal error with OptionalNullable[time.Time]#867
alexluong merged 3 commits intomainfrom
fix/go-sdk-metrics-unmarshal

Conversation

@alexluong
Copy link
Copy Markdown
Collaborator

@alexluong alexluong commented Apr 23, 2026

Summary

Fixes Go SDK crash when calling GetEventMetrics or GetAttemptMetrics with granularity:

json: cannot unmarshal string into Go value of type map[string]json.RawMessage

Root cause

Speakeasy's generated unmarshalValue in json.go has a bug with OptionalNullable[time.Time]. The type is map[bool]*time.Time under the hood. When unmarshalling a map field, the code checks if the map's value type is a "complex value type" (time.Time, big.Int, types.Date). If it is, it skips the standard json.Unmarshal path (which would correctly call OptionalNullable.UnmarshalJSON()) and falls through to custom map handling that tries to unmarshal a datetime string into map[string]json.RawMessage — which crashes.

This only affects OptionalNullable[time.Time]. Other OptionalNullable types like OptionalNullable[string] work fine because string is not a "complex value type". The marshal side already handles this correctly with a json.Marshaler check, but the unmarshal side is missing the equivalent json.Unmarshaler check. This is a Speakeasy codegen bug — we'll report it upstream.

Fix

  • OpenAPI spec: Changed time_bucket and granularity from nullable (type: [string, "null"]) to non-nullable (type: string). This makes Speakeasy generate *time.Time / *string instead of OptionalNullable[T], avoiding the buggy code path.
  • Server: Added omitempty to APIMetricsDataPoint.TimeBucket and APIMetricsMetadata.Granularity JSON tags so the server omits these fields when nil instead of returning null. This matches the updated spec.

API behavior change

When no granularity is specified, the response changes from:

{
  "data": [{"time_bucket": null, "dimensions": {}, "metrics": {"count": 100}}],
  "metadata": {"granularity": null, "query_time_ms": 5, ...}
}

to:

{
  "data": [{"dimensions": {}, "metrics": {"count": 100}}],
  "metadata": {"query_time_ms": 5, ...}
}

The fields are omitted instead of sent as null. Functionally equivalent for consumers — both result in nil/undefined when accessed.

Test plan

  • Regression test reproduces the original error before fix
  • SDK test passes after spec change (with value, absent, and null cases)
  • Server-side metrics tests pass with omitempty change
  • Speakeasy codegen will regenerate the SDK on merge

🤖 Generated with Claude Code

alexluong and others added 2 commits April 23, 2026 15:08
…me.Time]

The Speakeasy-generated unmarshalValue treats OptionalNullable[time.Time]
(map[bool]*time.Time) as a regular map with complex value types and tries
to unmarshal a datetime string into map[string]json.RawMessage, which fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OpenAPI spec used `type: [string, "null"]` for these fields, which
causes Speakeasy to generate `OptionalNullable[time.Time]` (map[bool]*T).
The Speakeasy unmarshalValue function has a bug where it treats this map
type with complex value types (time.Time) as a regular map and tries to
unmarshal a datetime string into map[string]json.RawMessage, causing:

  json: cannot unmarshal string into Go value of type map[string]json.RawMessage

Changing to non-nullable `type: string` generates `*time.Time` / `*string`
instead, which works correctly. The SDK still handles null values from the
API gracefully (null deserializes to nil pointer).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add omitempty to APIMetricsDataPoint.TimeBucket and
APIMetricsMetadata.Granularity so the server omits these fields
when nil, matching the non-nullable OpenAPI spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alexluong alexluong merged commit 5f91f47 into main Apr 23, 2026
2 checks passed
@alexluong alexluong deleted the fix/go-sdk-metrics-unmarshal branch April 23, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants