Skip to content

Sputnik: Vec<NestedStruct> serializes snake_case but Option<NestedStruct> is camelCase #2815

Closed
junobuild/juno-js
#895

Description

@AntonioVentilii

While working on a Sputnik satellite I ran into an inconsistency in how nested structs get serialized in typed query results, depending on the container.

An Option<Struct> field comes out camelCase on the JSON side, while a Vec<Struct> of the same record comes out snake_case. So you end up keeping two schemas for the same type, and switching an endpoint between opt and vec can fail at runtime with something like:

Error converting from js 'JsonData' into type 'Candid': missing field 'inviteCode'

From what I could tell it comes from functions-tools/src/converters/zod-to-rust.ts, where the vec case sets needsJsonData: false while opt passes through inner.needsJsonData, so array element structs don't get the #[json_data(nested)] attribute.

Flipping just that isn't enough though, the JsonData derive macro in junobuild/juno (derive.rs) doesn't seem to handle a nested Vec yet (nested_json_data_ident only recurses into Option).

Making vec camelCase would be breaking, so it might need to be opt-in or a major.

Activity

  1. changed the title [-]Sputnik: Vec<NestedStruct> serializes snake_case but Option<NestedStruct> is camelCase[/-] [+]Sputnik: `Vec<NestedStruct>` serializes snake_case but `Option<NestedStruct>` is camelCase[/+] on Jun 4, 2026
  2. peterpeterparker commented on Jun 5, 2026

    @peterpeterparker
    Contributor

    Can you provide a code snippet to reproduce the issue?

  3. AntonioVentilii commented on Jun 5, 2026

    @AntonioVentilii
    ContributorAuthor

    @peterpeterparker this is the practical case of my repo (simplified):

    const ItemSchema = j.strictObject({inviteCode: j.string()});
    
    export const getItem = defineQuery({
      result: j.strictObject({item: ItemSchema.optional()}),
      handler: () => ({item: {inviteCode: 'ABC123'}})
    });
    
    export const listItems = defineQuery({
      result: j.strictObject({items: j.array(ItemSchema)}),
      handler: () => ({items: [{inviteCode: 'ABC123'}]})
    });

    and it generates:

    pub struct GetItemResult {
        #[json_data(nested)]
        pub item: Option<GetItemResultItem>,
    }
    
    pub struct ListItemsResult {
        pub items: Vec<ListItemsResultItems>,  <-----here no json_data(nested)
    }

    Or maybe the real handlers affect this outcome? I made them hardcoded to simplify reproduction, but being sincere i did not test it with this specific case. I suppose, the results should be the same

  4. peterpeterparker commented on Jun 6, 2026

    @peterpeterparker
    Contributor

    Thanks for the issue and snippet, I can reproduce!

    I merged the PR and released the JS lib and integrated it in CLI. However it requires a bit more work as the macros should also be extended #2814. Means, it will require a full release (Juno, Sputnik, Juno Docker, Juno Action).

    I'll follow-up and let know when it's released, takes few hours.

  5. peterpeterparker commented on Jun 6, 2026

    @peterpeterparker
    Contributor

    The release is out and, using it, I wasn't able to reproduce the issue anymore - i.e. the problem seems to be resolved.

    Juno main repo, JS, CLI, Docker, Action and Sputnik have been released.

    On your local environment, you can update your CLI and then run juno emulator pull to download the latest version of the emulator - i.e. you'll need both CLI and emulator latest version.

    PS: Updating the emulator require to clear the state afterwards

    Thanks for the issue and help! Ping me if it ain't resolved on your side, happy to reopen and iterate further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions