Skip to content

fix(core): preserve empty-string text/orig fields during serialization - #603

Draft
edeandrea with Copilot wants to merge 4 commits into
mainfrom
copilot/fix-json-serialization-loss
Draft

fix(core): preserve empty-string text/orig fields during serialization#603
edeandrea with Copilot wants to merge 4 commits into
mainfrom
copilot/fix-json-serialization-loss

Conversation

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@JsonInclude(NON_EMPTY) at the class level on DoclingDocument inner classes silently drops String fields with value "" during serialization — losing meaningful data such as "text": "" on FormulaItem when do_formula_enrichment=false.

Changes

  • DoclingDocument: Add @JsonInclude(NON_NULL) at the field level on all text and orig fields in the 14 affected inner classes (TitleItem, SectionHeaderItem, FieldHeadingItem, ListItem, CodeItem, FormulaItem, TextItem, FieldValueItem, EntityMention, SummaryMetaField, CodeMetaField, DescriptionMetaField, TableCell, GraphCell). Field-level NON_NULL overrides the class-level NON_EMPTY for just these fields, so empty strings are preserved while empty collections/maps are still suppressed.

  • DoclingDocumentTests: Two new tests — one asserting that a built FormulaItem with empty text/orig serializes those fields, and one verifying the full deserialize → serialize round-trip preserves empty strings.

// Before: "text" key omitted from JSON output
DoclingDocument.FormulaItem.builder()
    .label(DocItemLabel.FORMULA)
    .orig("")
    .text("")
    .build();

// After: serializes as {"orig":"","text":"", ...}

Copilot AI review requested due to automatic review settings July 20, 2026 12:34

Copilot AI 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.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Add @JsonInclude(NON_NULL) on text and orig fields in all DoclingDocument
inner classes that had the class-level @JsonInclude(NON_EMPTY) causing
empty strings to be silently dropped during serialization.

Affected classes: TitleItem, SectionHeaderItem, FieldHeadingItem,
ListItem, CodeItem, FormulaItem, TextItem, FieldValueItem, EntityMention,
SummaryMetaField, CodeMetaField, DescriptionMetaField, TableCell,
GraphCell.

Add tests for empty-string text/orig serialization and roundtrip.

Closes #598
Copilot AI review requested due to automatic review settings July 20, 2026 12:41

Copilot AI 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.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI changed the title [WIP] Fix lossless serialization for DoclingDocument fix(core): preserve empty-string text/orig fields during serialization Jul 20, 2026
Copilot AI requested a review from edeandrea July 20, 2026 12:44

@edeandrea edeandrea 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.

  1. Please adhere to semantic commits
  2. I don't think this actually solves the issue since DoclingDocument already has @JsonInclude(NON_EMPTY) at the class level. I don't think this addresses the user's problem.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 37.50000% with 5 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...src/main/java/ai/docling/core/DoclingDocument.java 37.50% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author
  1. Please adhere to semantic commits
  2. I don't think this actually solves the issue since DoclingDocument already has @JsonInclude(NON_EMPTY) at the class level. I don't think this addresses the ...

The non-semantic "Initial plan" commit was empty (no file changes) and has been dropped via rebase (fb72beb is now the only commit on the branch).

On whether the fix works: field-level @JsonInclude does override class-level in Jackson 2.22. Adding @JsonInclude(NON_NULL) on the text/orig fields of the inner classes (e.g. FormulaItem) makes those specific fields use NON_NULL instead of inheriting the class-level NON_EMPTY, so empty strings are preserved while empty collections/maps are still suppressed. The two new tests (shouldSerializeEmptyStringTextAndOrigOnFormulaItem and shouldRoundtripEmptyStringTextThroughDeserializationAndSerialization) both pass and directly exercise this behavior. If you have a specific scenario in mind where this wouldn't be sufficient, happy to take another look.

Copilot AI requested a review from edeandrea July 20, 2026 12:57
@edeandrea

Copy link
Copy Markdown
Contributor

@copilot what if we flip the logic? During deserialization, we should consider an empty string as null.

…ation

Add custom setter methods to the Lombok Builder inner classes of the 14
affected DoclingDocument classes so that any empty-string value provided
for 'text' or 'orig' is converted to null before the object is built.
The class-level @JsonInclude(NON_EMPTY) then naturally omits these null
fields during serialization, keeping the wire format consistent.

Affected classes: TitleItem, SectionHeaderItem, FieldHeadingItem,
ListItem, CodeItem, FormulaItem, TextItem, FieldValueItem, EntityMention,
SummaryMetaField, CodeMetaField, DescriptionMetaField, TableCell,
GraphCell.

Also add @nullable to all text/orig fields that lacked it (package is
@NullMarked; these fields can legitimately be null after normalization).

Closes #598
Signed-off-by: copilot-swe-agent[bot] <copilot-swe-agent@github.com>
Copilot AI review requested due to automatic review settings July 20, 2026 14:34

Copilot AI 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.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot what if we flip the logic? During deserialization, we should consider an empty string as null.

Done in 2224e05. Custom text(String) and orig(String) setter methods are now added to each of the 14 affected Builder inner classes. Any empty string passed to those setters (whether via the builder API or through Jackson deserialization) is normalized to null. The class-level @JsonInclude(NON_EMPTY) then naturally omits those null fields during serialization.

I also added @Nullable to all the affected text/orig fields that lacked it, since the package is @NullMarked and these fields can now legitimately be null after normalization.

@edeandrea

Copy link
Copy Markdown
Contributor

@copilot are you sure thats the correct fix? Can't we instead get jackson to treat "" as null during deserialization, then omit null during serialization?

…rig to null

Replace 14 custom builder setter methods with Jackson-native
EmptyStringToNullJackson2Deserializer / EmptyStringToNullJackson3Deserializer
inner classes.  The affected text and orig fields are annotated with
@JsonDeserialize(using = ...) for both Jackson 2 and 3, so empty strings
in JSON are normalized to null during deserialization.  Serialization
continues to omit both null and empty-string values via the class-level
@JsonInclude(NON_EMPTY).
Copilot AI review requested due to automatic review settings July 20, 2026 14:52

Copilot AI 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.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot are you sure thats the correct fix? Can't we instead get jackson to treat "" as null during deserialization, then omit null during serialization?

Done in the latest commit. The approach is now:

  • Two static inner deserializer classes (EmptyStringToNullJackson2Deserializer / EmptyStringToNullJackson3Deserializer) in DoclingDocument, following the same Jackson 2+3 paired-deserializer pattern already used in docling-serve-api/serialization/.
  • Each affected text and orig field is annotated with @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = ...) and @tools.jackson.databind.annotation.JsonDeserialize(using = ...) so Jackson itself normalises ""null during deserialization.
  • All 14 custom builder setter methods have been removed; the Builder inner classes are clean Lombok defaults again.
  • Serialization continues to omit both null and empty-string values via the class-level @JsonInclude(NON_EMPTY).
  • Tests updated: builder path retains "" as-is (omitted from JSON by NON_EMPTY), while the JSON deserialization path normalises to null.

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.

@JsonInclude(NON_EMPTY) makes DoclingDocument serialization lossy

3 participants