Skip to content

Support UUID and other stringlike types as keys#435

Merged
quinnj merged 1 commit intoJuliaIO:masterfrom
treapster:string-types
Apr 2, 2026
Merged

Support UUID and other stringlike types as keys#435
quinnj merged 1 commit intoJuliaIO:masterfrom
treapster:string-types

Conversation

@treapster
Copy link
Copy Markdown
Contributor

@treapster treapster commented Feb 16, 2026

This PR fixes the following error with UUID and other types:

julia> using JSON, UUIDs, Dates

julia> JSON.json(Dict(uuid4() => "str"))
ERROR: ArgumentError: No key representation for UUID. Define StructUtils.lowerkey(::JSON.JSONStyle, ::UUID)
Stacktrace:
 [1] lowerkey(::JSON.JSONWriteStyle, x::UUID)
   @ JSON ~/.julia/packages/JSON/eypqd/src/write.jl:254
 [2] applyeach
   @ ~/.julia/packages/StructUtils/RnTYI/src/StructUtils.jl:578 [inlined]
 [3] json!(buf::Vector{…}, pos::Int64, x::Dict{…}, opts::JSON.WriteOptions{…}, ancestor_stack::Vector{…}, io::Nothing, ind::Int64, depth::Int64, bufsize::Int64)
   @ JSON ~/.julia/packages/JSON/eypqd/src/write.jl:671
 [4] json! (repeats 3 times)
   @ ~/.julia/packages/JSON/eypqd/src/write.jl:612 [inlined]
 [5] json(x::Dict{UUID, String}; pretty::Bool, kw::@Kwargs{})
   @ JSON ~/.julia/packages/JSON/eypqd/src/write.jl:482
 [6] json(x::Dict{UUID, String})
   @ JSON ~/.julia/packages/JSON/eypqd/src/write.jl:475
 [7] top-level scope
   @ REPL[3]:1
Some type informatio

The same error happens with Char, VersionNumber and some other types which should work out of the box.
For lowerkey I also changed Union{Float16, Float32, Float64} to Real, but if there is a reason why only specific types were supported we can revert it.

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.26%. Comparing base (f4fbb5a) to head (a9fccea).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #435   +/-   ##
=======================================
  Coverage   90.26%   90.26%           
=======================================
  Files           7        7           
  Lines        1366     1366           
=======================================
  Hits         1233     1233           
  Misses        133      133           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@treapster
Copy link
Copy Markdown
Contributor Author

@quinnj gentle ping in case you missed notification

@treapster
Copy link
Copy Markdown
Contributor Author

@quinnj ping

StructUtils.lowerkey(::JSONStyle, s::AbstractString) = s
StructUtils.lowerkey(::JSONStyle, sym::Symbol) = String(sym)
StructUtils.lowerkey(::JSONStyle, n::Union{Integer, Union{Float16, Float32, Float64}}) = string(n)
StructUtils.lowerkey(::JSONStyle, s::Union{StringLike, Real}) = string(s)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hmmmm, changing this to Real makes me a bit more nervous, just because now we're widening to a lot more types; can we keep this conservative and do:

Suggested change
StructUtils.lowerkey(::JSONStyle, s::Union{StringLike, Real}) = string(s)
StructUtils.lowerkey(::JSONStyle, s::Union{StringLike, Integer, Float16, Float32, Float64}) = string(s)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't see an issue with widening. I think it is expected for custom numbers to serialize the same way as builtin ones, and a reasonable default behavior. Don't we want to work out of the box for as many types as possible? Abstract types in Base are defined specifically so that a user can get as much behavior as he can, for free, by just inheriting from them. So, why instead of supporting that we want to give a user MethodError?

Copy link
Copy Markdown
Member

@quinnj quinnj left a comment

Choose a reason for hiding this comment

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

Sorry for the slow review; looks great! I had one request, but then I say we merge.

@quinnj quinnj merged commit b3e8dce into JuliaIO:master Apr 2, 2026
11 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