Skip to content

feat: align Lua encode with cjson#108

Merged
membphis merged 1 commit into
mainfrom
codex/issue-106-encode-cjson-compat
May 31, 2026
Merged

feat: align Lua encode with cjson#108
membphis merged 1 commit into
mainfrom
codex/issue-106-encode-cjson-compat

Conversation

@membphis
Copy link
Copy Markdown
Collaborator

@membphis membphis commented May 31, 2026

Summary

  • align plain Lua qjson.encode with lua-cjson for nil, sparse arrays, and numeric table keys
  • keep qjson-specific encode error wording so failures remain distinguishable from lua-cjson when both encoders coexist
  • detect recursive table/lazy-proxy cycles immediately with qjson.encode: circular reference, while preserving qjson.encode: max depth exceeded for truly deep acyclic inputs
  • preserve qjson extensions for lazy proxy encoding and int64/uint64 cdata decimal encoding from feat(encode): support LuaJIT int64_t/uint64_t cdata in qjson.encode #107, while continuing to reject other cdata shapes
  • add issue 106 Lua coverage for cjson-compatible behavior, qjson extensions, unsupported inputs, circular references, and shared non-recursive table references

Closes #106

Tests

  • PATH="/tmp/lua-qjson-luarocks-5.1/bin:$HOME/.luarocks/bin:$PATH" make test
  • cargo test --release --no-default-features
  • cargo test --features test-panic --release
  • make lint
  • PATH="/tmp/lua-qjson-luarocks-5.1/bin:$HOME/.luarocks/bin:$PATH" make lua-lint

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved circular reference detection in JSON encoding with clearer error messages
    • Fixed numeric object keys to stringify correctly in JSON output
    • Enhanced sparse array handling with null placeholders instead of rejection
    • Nil values now serialize to JSON null literal
  • Improvements

    • Better error reporting for encoding failures, excessive nesting, and unsupported input types

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors qjson.encode to align plain-table classification, nil serialization, and error messaging with lua-cjson while preserving lazy-proxy extensions. It introduces sparse-array threshold constants, threads cycle detection via an active map, adds classify_plain_table logic to decide array vs. object encoding based on positive contiguous integer keys, converts numeric object keys to strings, and serializes top-level nil as JSON null. Comprehensive new tests validate lua-cjson compatibility across input categories.

Changes

Encoder Refactor for lua-cjson Alignment

Layer / File(s) Summary
Sparse-array configuration and depth-error standardization
lua/qjson/table.lua
Introduces ENCODE_SPARSE_RATIO and ENCODE_SPARSE_SAFE constants for sparse-array thresholds, and centralizes depth-overflow errors via ENCODE_DEPTH_ERROR constant to match lua-cjson error text.
Cycle-detection infrastructure via active map
lua/qjson/table.lua
Updates lazy-object and lazy-array walking to thread the active table through recursive encode calls for circular-reference detection, replacing sole reliance on depth counters.
Table classification and numeric key handling
lua/qjson/table.lua
Adds classify_plain_table to distinguish arrays (positive contiguous 1..max keys within sparse limits) from objects; updates encode_array to accept explicit element-count parameter; updates encode_object to coerce numeric keys to strings and reject non-string/non-number key types.
Main encoder dispatch: nil-to-null and value-type routing
lua/qjson/table.lua
Refactors main dispatch so Lua nil serializes to JSON null, initializes the active cycle-detection table at the public _M.encode entrypoint, and ensures all value-type checks route through unified error paths.
New lua-cjson compatibility test suite
tests/lua/encode_cjson_compat_spec.lua
Adds comprehensive test suite with helpers for JSON structure comparison and error-message validation. Tests nil encoding, array classification with null holes and sparse-array rejection, numeric/non-positive/decimal key stringification in objects, empty-table behavior, cycle rejection with lua-cjson message alignment, lazy-proxy round-tripping, and unsupported input rejection across all type categories.
Existing test updates for error messages and behaviors
tests/lua/encode_depth_spec.lua, tests/lua/encode_errors_spec.lua, tests/lua/lazy_mutation_property_spec.lua
Updates expected error messages from "qjson.encode: max depth exceeded" to "Cannot serialise, excessive nesting" for depth/cycle cases; asserts numeric keys are stringified and sparse arrays within limits encode as JSON arrays with null placeholders; validates rejection of all cdata types.

Sequence Diagram

No sequence diagram is needed for this PR; the changes are primarily dispatch logic refactoring and table-classification decision trees, which are better represented by the flowchart in the review stack artifact.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #106: This PR implements the primary objective to align qjson.encode Lua input behavior with lua-cjson, including nil encoding, plain-table array/object classification with sparse limits, numeric key stringification, and nesting-depth error message alignment.
  • #104: The changes to array/object classification, cycle-detection via active map, and encoder dispatch directly affect qjson.materialize semantics and lazy-table mutation property validation tested in that issue.

Possibly related PRs

  • api7/lua-qjson#71: Both PRs refactor encoder depth tracking and cycle detection in lua/qjson/table.lua and update encode_depth_spec.lua expectations for nesting-error messages.
  • api7/lua-qjson#60: Both PRs modify lazy-object encoding paths (encode_lazy_object_walking) in lua/qjson/table.lua, overlapping on how lazy tables are walked during encoding.
  • api7/lua-qjson#54: Both PRs refactor table-encoding dispatch for plain tables using TABLE_TYPE_HINT and empty_array_mt to decide array vs. object encoding.
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "feat: align Lua encode with cjson" directly describes the main objective of aligning qjson.encode behavior with lua-cjson for plain Lua inputs, which is the core theme across all changed files.
Linked Issues check ✅ Passed The PR fully implements issue #106 requirements: centralizes nesting-depth errors, detects cycles with updated messaging, aligns plain table encoding with lua-cjson semantics (positive-integer arrays, stringified numeric keys, nil as JSON null), applies sparse-array limits, and adds comprehensive Lua test coverage validating all specified behaviors (D01, D06–D12, D15–D16) while preserving qjson lazy-proxy extensions (D13–D14).
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #106: the encoder refactoring aligns with lua-cjson semantics, test coverage validates specified behaviors and qjson extensions, and no unrelated modifications (e.g., configuration knobs, int64 encoding support, or unrelated refactoring) were introduced.
E2e Test Quality Review ✅ Passed Tests cover all encoding requirements with proper E2E flow using real data, encode-decode-compare assertions, and comprehensive error cases.
Security Check ✅ Passed Pure JSON encoding library with no security-sensitive code. All 7 categories (logging, DB, auth, access control, TLS, resource isolation, secrets) not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-106-encode-cjson-compat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@membphis membphis force-pushed the codex/issue-106-encode-cjson-compat branch from a89705a to 37c65f2 Compare May 31, 2026 04:11
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 31, 2026

CLA assistant check
All committers have signed the CLA.

@membphis membphis force-pushed the codex/issue-106-encode-cjson-compat branch 2 times, most recently from a97577f to 67e2766 Compare May 31, 2026 04:46
@membphis membphis force-pushed the codex/issue-106-encode-cjson-compat branch from 67e2766 to 0fa8886 Compare May 31, 2026 05:21
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lua/qjson/table.lua (1)

706-723: 💤 Low value

Unreachable TABLE_TYPE_HINT branches (optional cleanup).

The hint == "object" and hint == "array" branches at lines 712-717 appear unreachable:

  • TABLE_TYPE_HINT is only written at line 433 with value "array", immediately after setting empty_array_mt at line 432.
  • Since empty_array_mt is checked first (line 708), tables with TABLE_TYPE_HINT["array"] will never reach line 715.
  • TABLE_TYPE_HINT is never set to "object" anywhere in this file, and as a local, it cannot be set externally.

If this is intentional for future extensibility, consider adding a brief comment. Otherwise, these branches could be removed to simplify the code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lua/qjson/table.lua` around lines 706 - 723, The branches in
encode_plain_table that test TABLE_TYPE_HINT for "object" and "array" are
unreachable given TABLE_TYPE_HINT is only ever set to "array" when
empty_array_mt is assigned and empty_array_mt is checked first; either remove
the hint branches to simplify encode_plain_table (leaving the empty_array_mt
check and the classify_plain_table fallback) or, if you intend TABLE_TYPE_HINT
for future use, add a concise comment above TABLE_TYPE_HINT and/or inside
encode_plain_table explaining it’s reserved for extensibility so reviewers know
the branches are intentional; reference the symbols TABLE_TYPE_HINT,
encode_plain_table, empty_array_mt, and classify_plain_table when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lua/qjson/table.lua`:
- Around line 706-723: The branches in encode_plain_table that test
TABLE_TYPE_HINT for "object" and "array" are unreachable given TABLE_TYPE_HINT
is only ever set to "array" when empty_array_mt is assigned and empty_array_mt
is checked first; either remove the hint branches to simplify encode_plain_table
(leaving the empty_array_mt check and the classify_plain_table fallback) or, if
you intend TABLE_TYPE_HINT for future use, add a concise comment above
TABLE_TYPE_HINT and/or inside encode_plain_table explaining it’s reserved for
extensibility so reviewers know the branches are intentional; reference the
symbols TABLE_TYPE_HINT, encode_plain_table, empty_array_mt, and
classify_plain_table when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dec16cb1-076a-43eb-b386-c66d24459ca9

📥 Commits

Reviewing files that changed from the base of the PR and between a89705a and 0fa8886.

📒 Files selected for processing (5)
  • lua/qjson/table.lua
  • tests/lua/encode_cjson_compat_spec.lua
  • tests/lua/encode_depth_spec.lua
  • tests/lua/encode_errors_spec.lua
  • tests/lua/lazy_mutation_property_spec.lua
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/lua/encode_depth_spec.lua

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.

spec: align qjson.encode Lua input behavior with lua-cjson

2 participants