Repository navigation
fix: omit null-valued event properties - #1047
Merged
turnipdabeets merged 2 commits intoNov 9, 2026
Merged
Conversation
Drop properties whose value is NSNull (or a nil boxed in Any) from analytics
and AI events when they are written to the queue and again when they are
sent, recursing into nested dictionaries and dictionaries inside arrays.
Null array elements keep their index and emptied dictionaries stay {}.
$set, $set_once and $group_set values are left as given so a caller can
still clear a person or group property.
Implements the sdk-specs capture requirement "Capture drops null-valued
object properties" and capture-ai's matching requirement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
posthog-ios Compliance ReportDate: 2026-11-09T18:36:02.603191+00:00 ✅ All Tests Passed!234/234 tests passed Capture_V1 Tests✅ 106/106 tests passed View Details
Capture_Ai_V1 Tests✅ 111/111 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
Contributor
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 51fa0ac: 1 Should fix.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
A dictionary with number or boolean keys, such as one added in beforeSend, skipped the null cleanup and was then stringified by sanitizeDictionary, so its null members reached the queue as JSON null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dustinbyrne
approved these changes
Nov 9, 2026
6 of 18 tasks
This was referenced Nov 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
sdk-specs
capturerequires Capture drops null-valued object properties, andcapture-airequirescaptureAito follow it. iOS sentNSNull()property values as JSONnull, on the wire and in the disk queue. This is the only gap on the iOS Capture and Capture AI compliance rows in sdk-specs #187. Android and Flutter already drop these.Changes
NSNull(andnilboxed inAny) property values when they're written to the disk queue and again when they're sent, afterbeforeSend. The send-time pass also cleans events queued by older versions.{}, andfalse,0,"","null"and empty containers are unchanged.capture,captureAi,screen,captureException,$feature_flag_called, and so on. Session replay snapshots aren't touched.NSNull().Scope choice on
$set/$set_once/$group_set: the spec covers "custom event object properties" and says nothing about person or group properties. An iOS caller may pass$set: ["plan": NSNull()]to clear a person property, so these three keys are left as given, and a test pins that.Behavior change worth noting: SDK-set nulls are dropped too.
$feature_flag_calledfor a flag with no value now has no$feature_flag_responseinstead ofnull. That matches posthog-js, which leaves itundefined. One existing test was updated for this.Performance: one extra pass over the properties at enqueue and at send. Unchanged containers aren't copied. A local benchmark on an extreme payload (10k top-level properties plus 2,000 message dictionaries) took about 15 ms per pass, mostly the existing
isNullValueboxed-nil check.💚 How did you test it?
New
Capture V1 null propertiessuite (PostHogCaptureNullPropertiesTest) inPostHogCaptureV1Test.swift, built on the spec scenarios. It coverscapture,captureAi, nulls added bybeforeSend, the disk encoding of the analytics and AI queues, dictionaries with non-string keys, events queued before this change being cleaned on send, and$set/$set_once/$group_setnulls being kept.iOS simulator (iPhone Air, iOS 26.4.1).
xcodebuild testhung before connecting on this machine ("The test runner hung before establishing connection"), so I built the bundle and ran it inside the simulator:0ce74f425)05721bbdcwith onlyPostHogCaptureV1Test.swiftfrom this branchtest/hookNullpresent, nested{"drop":null}kept,{"1":null}stored). The$set/$set_once/$group_setguard passes before and after.-XCTest All), this branchPostHogCaptureV1OptionsTest) then passed 18/18 when rerun on its own. The test doesn't involve null values, and it passes in CI.App smoke test on the same simulator. The
PostHogExampleapp was temporarily pointed at a local capture server (that change isn't committed). It calledcaptureandcaptureAiwithNSNull()values at the top level, in a nested dictionary, in an array, and in a dictionary inside an array, plus$set: ["plan": NSNull()]. Both/i/v1/analytics/eventsand/i/v1/ai/eventsreceivedtestabsent,nested: {"keep": 1},items: ["1", null, 2, {}, [null]],""/0/false/"null"/[]unchanged, and$set: {"plan": null}.macOS SPM:
make test filter="'PostHogCaptureNullPropertiesTest|PostHogPropertiesSerializationTests|PostHogCaptureV1Test|UtilsTest'": 116/116 passed.isFeatureEnabledDoesNotReportDefaulttest was updated because$feature_flag_responseis now absent instead ofnull. It passes.make testran while other test runs shared the machine and failed in unrelated suites. The two app-groupPostHogStorageTestcases fail the same way on the base branch, and the rest passed when rerun on their own.make lint: passes. The warnings it prints are already on the base branch.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file (added a bullet to the existing 4.0 changeset)🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code (Opus 5.5) implemented this from the sdk-specs requirement, following the scope set by the requester. It encodes in two places, at enqueue and at send, so that both the disk queue and events left over from older versions meet the spec.
🤖 Generated with Claude Code