Skip to content

Commit 3bac427

Browse files
authored
Add JSON3.tostring for Real number overloads when writing (#236)
* Add `JSON3.tostring` for Real number overloads when writing This is the best idea I can come up with to address #232. First a summary of the problem, * We allow custom number types to overload `StructTypes.numbertype` that we convert to before writing JSON * However, we only provide JSON writing routines for `Integer` and `AbstractFloat` * So a number like `3//2` (`Rational`) or `π` (`Irrational`) which are both `<: Real`, but not `Integer` or `AbstractFloat` we get the stack overlfow behavior reported above The proposal here then is we first check if a `Real` has overloaded `StructTypes.numbertype` and _if not_, we'll use a new `JSON3.tostring` method, which by default converts the custom number to `Float64` and then calls `Base.string(x)`. This method allows custom number types, like FixedPointDecimal, to overload `JSON3.tostring` to provide an appropriate string representation for their type. * add tostring to docs
1 parent 3d542d3 commit 3bac427

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ JSON3.AlignmentContext
182182
JSON3.Object
183183
JSON3.Array
184184
Base.copy
185+
JSON3.tostring
185186
```
186187

187188
### In Relation to Other JSON Packages

src/write.jl

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,37 @@ function write(::NumberType, buf, pos, len, y::Integer; kw...)
210210
return buf, pos + n, len
211211
end
212212

213-
write(::NumberType, buf, pos, len, x::T; kw...) where {T} =
214-
write(NumberType(), buf, pos, len, StructTypes.construct(StructTypes.numbertype(T), x); kw...)
213+
# default fallback for non-Integer/non-AbstractFloat numbers
214+
# conver to Float64 and write the string representation
215+
"""
216+
JSON3.tostring(x::Real) -> String
217+
218+
For some number types that are `<: Real`, but not `<: Integer` or `<: AbstractFloat`,
219+
`tostring` provides an overload to convert `x` to an appropriate string representation
220+
which will be used for the JSON numeric representation. By default, `x` is converted
221+
to a `Float64` and then to a `String`.
222+
"""
223+
tostring(x::Real) = Base.string(convert(Float64, x))
224+
225+
function write(::NumberType, buf, pos, len, x::T; kw...) where {T}
226+
NT = StructTypes.numbertype(T)
227+
if NT === T
228+
# this means T hasn't overloaded numbertype
229+
# and we can't StructTypes.construct since we'll just stack overflow
230+
# so we call last-chance tostring that can be overloaded
231+
bytes = codeunits(tostring(x))
232+
sz = sizeof(bytes)
233+
@check sz
234+
for i = 1:sz
235+
@inbounds @writechar bytes[i]
236+
end
237+
238+
return buf, pos, len
239+
else
240+
write(NumberType(), buf, pos, len, StructTypes.construct(NT, x); kw...)
241+
end
242+
end
243+
215244
function write(::NumberType, buf, pos, len, x::AbstractFloat; allow_inf::Bool=false, kw...)
216245
isfinite(x) || allow_inf || error("$x not allowed to be written in JSON spec")
217246
bytes = codeunits(Base.string(x))

test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,4 +1062,8 @@ s1_json = String(take!(iob))
10621062
s2 = JSON3.read(s1_json, Struct1)
10631063
@test s1.iarr == s2.iarr
10641064

1065+
# https://github.com/quinnj/JSON3.jl/issues/232
1066+
@test JSON3.write(3//2) == "1.5"
1067+
@test JSON3.write(π) == "3.141592653589793"
1068+
1
10651069
end # @testset "JSON3"

0 commit comments

Comments
 (0)