Skip to content

[BUG][scala-sttp][circe] format: byte (Base64) properties fail to deserialize - Decoder[Array[Byte]] expects JSON array #23618

@nikhilsu

Description

@nikhilsu

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
Description

When a schema property uses type: string, format: byte (Base64-encoded binary data in JSON), the scala-sttp generator with jsonLibrary: circe produces Option[Array[Byte]] for the field type. At runtime, circe's derived Decoder[Array[Byte]] expects a JSON array of numbers ([10, -20, 30, ...]), but the actual wire format for format: byte is a Base64-encoded JSON string (e.g. "rO0ABX..."). Decoding fails with:

DecodingFailure at .fieldName: Array[Byte]

The same issue affects format: binary properties used in JSON object schemas (not multipart bodies): they generate Option[File], whose FileDecoder also relies on Decoder[Array[Byte]] and fails identically.

No Decoder[Array[Byte]] is provided in AdditionalTypeSerializers that handles Base64 strings, leaving the field permanently undecodable regardless of input.

openapi-generator version

Master (6be478dd7d8)

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  title: Byte Test
  version: 1.0.0
paths: {}
components:
  schemas:
    Payload:
      type: object
      properties:
        checksum:
          type: string
          format: byte
Generation Details
generatorName: scala-sttp
additionalProperties:
  jsonLibrary: circe
Steps to reproduce
  1. Generate a client from the spec above with jsonLibrary: circe
  2. Call an endpoint that returns {"checksum": "rO0ABXNy..."}
  3. Observe DecodingFailure at .checksum: Array[Byte]
Related issues/PRs
Suggest a fix

Add a FlexibleByteArrayDecoder: Decoder[Array[Byte]] to additionalTypeSerializers.mustache that tries a JSON number array first (strict OAS compliance), then falls back to Base64 string decoding (Jackson's actual wire format):

implicit final lazy val FlexibleByteArrayDecoder: Decoder[Array[Byte]] =
  Decoder.decodeArray[Byte].or(Decoder.decodeString.emap { s =>
    try Right(Base64.getDecoder.decode(s))
    catch { case _: IllegalArgumentException => Left(s"Cannot decode '$s' as Base64 Array[Byte]") }
  })

This also fixes FileDecoder for format: binary JSON properties, since it internally calls Decoder[Array[Byte]].

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions