Skip to content

docs(readme): document SDK JSON mapper - #109

Merged
GregHolmes merged 11 commits into
mainfrom
gh/document-object-mapper
Sep 18, 2026
Merged

GregHolmes merged 11 commits into
mainfrom
gh/document-object-mapper

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • document ObjectMappers.JSON_MAPPER as the SDK-configured mapper for request and response objects
  • explain that it includes the Jackson modules required for Java Optional and date/time fields
  • note that generated model toString() output is suitable for pretty-printed debugging

A focused probe confirmed that serializing a generated V1ConnectOptions object with plain new ObjectMapper() throws InvalidDefinitionException.

Validation

  • ./gradlew spotlessCheck test compileExamples
  • mvn test

The JSON serialization snippet referenced an undefined `request` and did
not show the read direction, though the section text promises "request or
response objects". Add the builder call that defines `request`, add a
`readValue` example for parsing a response payload, and note that both
mapper calls throw `JsonProcessingException`. Unwrap the paragraph to one
line to match the rest of the README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The claim checks out, and I verified it by running it rather than reading it.

What I checked

  • Does a plain mapper really fail? Yes. Serializing a V1ConnectOptions with new ObjectMapper() throws exactly the exception this section names: InvalidDefinitionException: Java 8 optional type java.util.Optional<com.deepgram.types.ListenV1Callback> not supported by default. JSON_MAPPER handles the same object fine.
  • Is "can throw" the right hedge, rather than "will throw"? Yes, and it matters. A ListenV1RequestUrl with only url set serializes cleanly on a plain mapper, because no optional field is populated. It fails for some objects and not others, so "can" is accurate as written.
  • Does the mapper register what the text says it registers? Yes — ObjectMappers.java:18-19 adds Jdk8Module and JavaTimeModule, precisely the two things the sentence names.
  • Are the date/time fields real, or is that half hypothetical? Real. 16 files under src/main/java use java.time; e.g. CreateKeyV1Response.java:35 holds an Optional<OffsetDateTime> expirationDate.

Two fixes I pushed (3c4471b)

  1. The snippet couldn't be pasted. request was never defined in the section, and writeValueAsString throws a checked JsonProcessingException. Worth knowing: ReadmeSnippetsCompileTest cannot catch either problem — it injects request as a static field (ReadmeSnippetsCompileTest.java:171) and wraps every snippet in compileOnly() throws Exception (:213), so a snippet can pass the test while failing a literal copy-paste. The snippet now defines request via a builder call, and a comment flags the checked exception (matching how the README already handles Files.readAllBytes and Thread.sleep).
  2. The first sentence promises "request or response objects" but only the write direction was shown, and parsing is the more common need. Reading fails identically — a plain readValue on a CreateKeyV1Response payload threw the same exception, while JSON_MAPPER parsed it including an unrecognized field. Added a readValue line.

All 8 checks pass on the pushed commit.

One question, not blocking

ObjectMappers.java is Fern-generated and is not in .fernignore, so its shape is whatever the generator emits next cycle — we're now telling developers to depend on a class we don't pin. The README already points at four other com.deepgram.core classes (DeepgramTransport, DeepgramTransportFactory, Environment, DeepgramApiHttpResponse), so this is consistent with existing practice rather than something new, and nothing in AGENTS.md or CONTRIBUTING.md declares core internal. Not holding the PR on it — just want your yes that JSON_MAPPER keeps its name and location across regens.

Optional, take or leave

  • Every generated type's toString() already returns pretty-printed JSON via ObjectMappers.stringify (ObjectMappers.java:29), which is the shorter answer for logging and debugging. One sentence would save a reader the mapper call.
  • The section is reference material sitting inside Quickstart, between auth setup and the feature walkthroughs. It may belong next to "Raw Response Access" where the other plumbing topics live.

@GregHolmes

Copy link
Copy Markdown
Collaborator Author

@dg-coreylweathers Final review items are resolved in 1d2e7e1: DeepgramJson.MAPPER is now a permanently frozen public facade, the README guidance sits with raw response access, and it documents the toString() debugging path. ./gradlew spotlessCheck test compileExamples, mvn test, and the Java 11/17/21 CI matrix all pass. Could you re-review?

@GregHolmes

Copy link
Copy Markdown
Collaborator Author

@dg-coreylweathers Correction to my earlier note: the DeepgramJson facade and its .fernignore entries were unnecessary and have been removed in 5b86594. The final README documents the existing generator-owned ObjectMappers.JSON_MAPPER, retaining the paste-ready exception handling, accurate Management response description, secret-handling warning, and toString() debugging guidance. ./gradlew spotlessCheck test compileExamples and mvn test pass.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving with one small fix and three optional nits. Re-verified by running the snippet, not reading it.

What I checked

  • Is the failure claim true? Yes. A plain new ObjectMapper() parsing the snippet's own payload throws InvalidDefinitionException: Java 8 optional type java.util.Optional<java.lang.String> not supported by default. The snippet's writeValueAsString line actually succeeds on a plain mapper — only url is set, so no Optional field is populated — which makes "can throw" the accurate hedge and the readValue line the one that demonstrates it.
  • Does the snippet survive a literal copy-paste? Yes. Pasted verbatim into a method declaring no throws clause, it compiled and ran. The explicit try/catch (JsonProcessingException) fixes this properly rather than relying on ReadmeSnippetsCompileTest's throws Exception wrapper.
  • Is the new toString() claim true? Yes. CreateKeyV1Response.toString() delegates to ObjectMappers.stringify, which uses writerWithDefaultPrettyPrinter(); the output came back multiline and re-parsed as valid JSON. (Worth knowing: the .NET SDK's equivalent runs Regex.Unescape over the output and produces invalid JSON. Java does not.)
  • Is recommending JSON_MAPPER and toString() together safe? Yes, and this needed checking: stringify calls setSerializationInclusion(ALWAYS) on the shared static mapper, mutating global state. I serialized a request, called toString() on a model, and re-serialized — byte-identical, because the class-level @JsonInclude(NON_ABSENT) on each generated model overrides the mapper-level setting.
  • Is the reference safe against a Fern regen? Yes. ObjectMappers is generator-owned and not in .fernignore, but ReadmeSnippetsCompileTest compiles this snippet, and RegenTypesTest and AgentSettingsProviderDefaultTest also reference ObjectMappers.JSON_MAPPER — a rename or relocation fails CI at compile time. That closes my round-1 question more durably than the withdrawn facade would have.
  • Also verified: the payload's api_key_id / key / expiration_date match the model's @JsonProperty names; CreateKeyV1Response is genuinely the return type of manage().v1().projects().keys().create(...); a repo-wide grep finds no conflicting mapper guidance and no leftover DeepgramJson references after the revert; main has not touched README.md since the merge base. ./gradlew spotlessCheck test compileExamples passed in eclipse-temurin:17-jdk.

One fix before merge

The section is filed as a subsection of ## Error Handling. A reader scanning the table of contents for serialization guidance finds it only by reading about errors first. Change ### to ## on README.md:661 — it is already the last block before ## Complete SDK Reference, so no text moves, and ReadmeSnippetsCompileTest still collects the snippet (it takes the H2 name when no H3 is open). This is fallout from my own earlier suggestion being followed literally: "next to Raw Response Access" was the right neighborhood, that section just happens to live under Error Handling.

Three nits, take or leave

  • README.md:663 — "when serializing SDK request or response objects" covers only the write direction, but the half that fails on a plain mapper is the parse. "when reading or writing SDK request and response objects" is accurate to what the snippet shows.
  • README.md:663 — the mapper also disables FAIL_ON_UNKNOWN_PROPERTIES, which is why it tolerates response fields an older SDK build does not know about. That is the practical reason to use it when parsing an evolving API.
  • README.md:676 and :680 — json and parsed are assigned and never used; a literal paste emits unused-variable warnings in strict builds.

One note for the record

Your second comment describes DeepgramJson.MAPPER as a permanently frozen facade and your third removes it as unnecessary. The final state is the better one and the compile-time guard above is why. A line saying so would keep the thread from reading as an unexplained reversal.

Keeping the // Never log the returned key value. comment as-is: it sits directly above a payload carrying a key field, which is the one value in a key-creation response that is a live credential returned exactly once.

@GregHolmes

GregHolmes commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator Author

Record clarification: 1d2e7e1 added a facade in response to the generator-stability concern. After confirming that existing compile-time tests already guard ObjectMappers.JSON_MAPPER, 5b86594 removed that unnecessary facade. The final README intentionally documents the established generator-owned mapper; 652e9d1 applies the remaining review fix and nits.

dg-coreylweathers and others added 2 commits September 18, 2026 07:24
Fixes three accuracy problems and four copy nits in the JSON section.

Accuracy:
- Unknown-field tolerance was credited to the mapper. A mapper carrying only
  Jdk8Module and JavaTimeModule, with FAIL_ON_UNKNOWN_PROPERTIES left at
  Jackson's default of enabled, parses a payload with an unmodeled field
  because all 276 generated models declare
  @JsonIgnoreProperties(ignoreUnknown = true) on their deserialization
  builder. Restated as a property of the response, and noted that the
  unrecognized fields survive on additionalProperties.
- The toString() sentence offered two outcomes. 270 of 414 classes defining
  toString() delegate to ObjectMappers.stringify; the other 144 are union and
  alias wrappers that return the wrapped value instead
  (SpeakSettingsV1Provider yields "SpeakSettingsV1Provider{value: {...}}",
  ErrorResponse.of("boom") yields "boom"). Claim scoped accordingly.
- The InvalidDefinitionException claim now names a case that reproduces
  (parsing a CreateKeyV1Response) rather than asserting it for any SDK type;
  writing a ListenV1RequestUrl with a populated Optional does not throw.

Copy:
- Heading renamed to "Working with JSON"; the section covers parsing too.
- Snippet binds and uses both results, and demonstrates toString().
- Prose split into two shorter paragraphs ending in a colon, matching the
  surrounding sections.
- Redaction warning promoted to the file's > **Note:** callout pattern.

./gradlew spotlessCheck test compileExamples passes in eclipse-temurin:17-jdk;
ReadmeSnippetsCompileTest still collects the snippet under the renamed H2, and
the snippet compiles and runs pasted verbatim into a method with no throws
clause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@GregHolmes
GregHolmes merged commit eeb39b4 into main Sep 18, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants