From 307aabbc4c00a4f8ae6e3242e7f084297b39b2f0 Mon Sep 17 00:00:00 2001 From: Fengyang Wang Date: Thu, 27 Apr 2017 18:08:21 -0400 Subject: [PATCH] Fix #201, support serializing Enums --- src/Writer.jl | 30 +++++++++++++++++++++--------- test/lowering.jl | 10 ++++++++++ test/runtests.jl | 6 ++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/Writer.jl b/src/Writer.jl index 63f7333..c686215 100644 --- a/src/Writer.jl +++ b/src/Writer.jl @@ -17,18 +17,15 @@ immutable CompositeTypeWrapper{T} end CompositeTypeWrapper(x) = CompositeTypeWrapper(x, fieldnames(x)) -const JSONPrimitive = Union{ - Associative, Tuple, AbstractArray, AbstractString, Integer, - AbstractFloat, Void, CompositeTypeWrapper} - """ lower(x) Return a value of a JSON-encodable primitive type that `x` should be lowered into before encoding as JSON. Supported types are: `Associative` to JSON objects, `Tuple` and `AbstractVector` to JSON arrays, `AbstractArray` to nested -JSON arrays, `AbstractString` to JSON string, `Integer` and `AbstractFloat` to -JSON number, `Bool` to JSON boolean, and `Void` to JSON null. +JSON arrays, `AbstractString`, `Symbol`, `Enum`, or `Char` to JSON string, +`Integer` and `AbstractFloat` to JSON number, `Bool` to JSON boolean, and +`Void` to JSON null, or any other types with a `show_json` method defined. Extensions of this method should preserve the property that the return value is one of the aforementioned types. If first lowering to some intermediate type is @@ -54,8 +51,14 @@ if VERSION < v"0.5.0-dev+2396" lower(f::Function) = "function at $(f.fptr)" end -lower(c::Char) = string(c) -lower(d::Type) = string(d) +# To avoid allocating an intermediate string, we directly define `show_json` +# for this type instead of lowering it to a string first (which would +# allocate). However, the `show_json` method does call `lower` so as to allow +# users to change the lowering of their `Enum` or even `AbstractString` +# subtypes if necessary. +const IsPrintedAsString = Union{Char, Type, AbstractString, Enum, Symbol} +lower(x::IsPrintedAsString) = x + lower(m::Module) = throw(ArgumentError("cannot serialize Module $m as JSON")) lower(x::Real) = Float64(x) @@ -241,7 +244,16 @@ end show_pair(io::JSONContext, s, kv) = show_pair(io, s, first(kv), last(kv)) # Default serialization rules for CommonSerialization (CS) -show_json(io::SC, ::CS, x::Union{AbstractString, Symbol}) = show_string(io, x) +function show_json(io::SC, s::CS, x::IsPrintedAsString) + # We need this check to allow `lower(x::Enum)` overrides to work if needed; + # it should be optimized out if `lower` is a no-op + lx = lower(x) + if x === lx + show_string(io, x) + else + show_json(io, s, lx) + end +end function show_json(io::SC, s::CS, x::Union{Integer, AbstractFloat}) # workaround for issue in Julia 0.5.x where Float32 values are printed as diff --git a/test/lowering.jl b/test/lowering.jl index ae3daf3..6aae3eb 100644 --- a/test/lowering.jl +++ b/test/lowering.jl @@ -27,4 +27,14 @@ JSON.lower{T}(v::Type151{T}) = Dict(:type => T, :value => v.x) fixednum = Fixed{Int16, 15}(0.1234) @test JSON.parse(JSON.json(fixednum)) == Float64(fixednum) +# test that the default string-serialization of enums can be overriden by +# `lower` if needed +@enum Fruit apple orange banana +JSON.lower(x::Fruit) = string("Fruit: ", x) +@test JSON.json(apple) == "\"Fruit: apple\"" + +@enum Vegetable carrot tomato potato +JSON.lower(x::Vegetable) = Dict(string(x) => Int(x)) +@test JSON.json(potato) == "{\"potato\":2}" + end diff --git a/test/runtests.jl b/test/runtests.jl index 7bba04b..df46a7b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -264,6 +264,12 @@ end @test json('\n') == "\"\\n\"" @test json('🍩') =="\"🍩\"" +# check enums +@enum Animal zebra aardvark horse +@test json(zebra) == "\"zebra\"" +@test json([aardvark, horse, Dict("z" => zebra)]) == + "[\"aardvark\",\"horse\",{\"z\":\"zebra\"}]" + # check for issue #163 @test Float32(JSON.parse(json(2.1f-8))) == 2.1f-8