Skip to content

[TypeScript] JSON parts of a multipart request skip the serializer, so with noSerdeLayer: false their keys go out camelCase #18373

Open

Description

@coreyatpulse

Component

SDKs

Priority

P1 - High (Strongly needed)

SDK Language(s)

TypeScript

Bug Description

What happens

An object part of a multipart/form-data body is sent with the SDK's camelCase property names instead of the wire names. JSON request bodies are serialized correctly, so the same object goes out in different shapes depending on the content type.

Repro

openapi: 3.1.0
info: {title: Repro, version: "1"}
paths:
  /upload:
    post:
      operationId: upload
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: {type: string, format: binary}
                options: {$ref: "#/components/schemas/Options"}
            encoding:
              options: {contentType: application/json}
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: {type: object, properties: {ok: {type: boolean}}}
components:
  schemas:
    Options:
      type: object
      properties:
        page_range: {type: string}
groups:
  ts:
    generators:
      - name: fernapi/fern-typescript-node-sdk
        version: 3.107.3
        output: {location: local-file-system, path: ../out}
        config: {namespaceExport: Repro, noSerdeLayer: false}
await client.upload({ file: new Blob(["%PDF-1.4"]), options: { pageRange: "1-2" } });

The body sent:

Content-Disposition: form-data; name="options"

{"pageRange":"1-2"}

Expected {"page_range":"1-2"}. The serializer for Options maps the name correctly (core.serialization.property("page_range", …)), but the generated client doesn't use it:

if (request.options != null) {
    _body.append("options", toJson(request.options));
}

That code comes from the json branch of appendPropertyToFormData.ts, which always calls toJson on the raw value.

Suggested fix

When the serde layer is on, serialize the part before encoding it, e.g. toJson(serializers.Options.jsonOrThrow(request.options, { … })), as the JSON-body path does.

Also, the spec's encoding.options.contentType: application/json isn't applied: the part goes out with no Content-Type. Some servers need it to tell a JSON part from a plain-text one.

Environment: macOS 26 (arm64), Node.js 26.4, generators run locally in Docker (fern generate --local). Node.js fetch (undici) FormData.

Versions

Fern CLI version: 5.163.1
SDK Generator versions:

  • fernapi/fern-typescript-node-sdk 3.107.3

Workaround

noSerdeLayer: true sends wire names but gives up camelCase. Callers can also pass wire-named keys, which the types reject.

Are you interested in contributing a fix?

Yes

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions