All embed-bearing appview responses fail to deserialize — $type not first and AllowOutOfOrderMetadataProperties unset #50

Closed
opened 2026-07-05 20:40:50 +00:00 by Grandiras · 0 comments
Owner

app.bsky.feed.getPosts/getPostThread/timeline responses from the public Bluesky appview serialize embed views with the discriminator LAST (property order: ['external', '$type']). System.Text.Json's [JsonPolymorphic] requires the discriminator first unless JsonSerializerOptions.AllowOutOfOrderMetadataProperties = true (.NET 9+), so every response containing an embed throws:

NotSupportedException: The JSON payload for polymorphic interface or abstract type 'ATProtoNet.Lexicon.App.Bsky.Embed.EmbedView' must specify a type discriminator. Path: $.posts[0].embed

Repro: client.Bsky.Feed.GetPostsAsync(["at://did:plc:tkjlutt3jh2aqkkxliitff6k/app.bsky.feed.post/3m3micwvzml2b"]) against https://public.api.bsky.app.

Fix: set AllowOutOfOrderMetadataProperties = true in AtProtoJsonDefaults.CreateOptions() and LexiconTypeRegistry.CreateOptions(). Real-world writers also put $type last in record-internal unions (observed in exchange.recipe attribution objects), so this hardens record reads too. Found by Mise's engagement hydration (anchor share-post resolution silently failed).

`app.bsky.feed.getPosts`/`getPostThread`/timeline responses from the public Bluesky appview serialize embed views with the discriminator LAST (property order: `['external', '$type']`). System.Text.Json's `[JsonPolymorphic]` requires the discriminator first unless `JsonSerializerOptions.AllowOutOfOrderMetadataProperties = true` (.NET 9+), so every response containing an embed throws: ``` NotSupportedException: The JSON payload for polymorphic interface or abstract type 'ATProtoNet.Lexicon.App.Bsky.Embed.EmbedView' must specify a type discriminator. Path: $.posts[0].embed ``` Repro: `client.Bsky.Feed.GetPostsAsync(["at://did:plc:tkjlutt3jh2aqkkxliitff6k/app.bsky.feed.post/3m3micwvzml2b"])` against `https://public.api.bsky.app`. Fix: set `AllowOutOfOrderMetadataProperties = true` in `AtProtoJsonDefaults.CreateOptions()` and `LexiconTypeRegistry.CreateOptions()`. Real-world writers also put `$type` last in record-internal unions (observed in exchange.recipe attribution objects), so this hardens record reads too. Found by Mise's engagement hydration (anchor share-post resolution silently failed).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/ATProto.NET#50
No description provided.