Saving complex numbers using JSON3

I am trying to save Complex numbers in json files formatted in a python compatible way as

using JSON3
c = 1+5im
d = Dict("c" => c)
jsonstring = JSON3.pretty(d)
#= should be
{
    "c" : 1+5j
}
=#

This is how far I got, using the documentation

using JSON3
using StructTypes

StructTypes.StructType(::Type{Complex{RT}}) where {RT<:Real} = JSON3.RawType() 

function StructTypes.construct(
         ::Type{Complex{RT}}, x::JSON3.RawValue) where {RT<:Real}
    return parse(Complex{RT}, unsafe_string(pointer(x.bytes, x.pos), x.len))
end

function JSON3.rawbytes(x::Complex{RT}) where {RT<:Real}
    str = x.im > zero(RT) ? "$(x.re)+$(x.im)j" : "$(x.re)$(x.im)j"
    return transcode(UInt8, str)
end

c = 1+5im
d = Dict("c" => c)

JSON3.write(d)
# works fine and returns "{\"c\":1+5j}"

JSON3.read(JSON3.write(c), Complex{Int})
# works fine and returns the original number

JSON3.pretty(d)
#= crashes with
ERROR: ArgumentError: invalid JSON at byte position 7 while parsing type JSON3.Object:
ExpectedComma {"a":1+7j}
=#

Is it possible to create such a JSON serialisation of complex numbers using JSON3? And if yes, what am I missing to make JSON3.pretty work here?

I think this also relates to the more general question: If I define a serialisation via rawbytes for a type, how do I tell JSON3 to use the correct deserialisation routine? It seems that the error I am getting stems from the unexpected “+” sign in the json string.

What are you using on the Python side that expects this format? The problem seems to be that it is not valid JSON according to the spec.

1 Like

So far the python side does not exist yet, but the idea was to write our own decoder that is happy with this format. But given that this is not valid json it might be better to do {"c": "1+5j"} instead, i.e. serialise complex numbers as their string representation.

Nevertheless, if what I originally wanted is possible

Ah, this is because JSON3.pretty ends up calling JSON3.read while printing. I can’t remember the exact details since @fedorc recently reworked that code. It seems like we could give you the ability to pass in a type name or something and still have this work.

Rust’s Serde has 3 different ways of serializing a type name: internally, externally, and adjacently tagged.

https://serde.rs/enum-representations.html

JSON allows objects, so on the Julia side you can define a suitable struct and write it into JSON, like this:

using JSON3, StructTypes

struct JCX
    real::Float64
    imaginary::Float64
end
JCX(x) = JCX(reim(x)...)

StructTypes.StructType(::Type{JCX}) = StructTypes.Struct()

c = 2.72+3.14im

j = JSON3.write(JCX(c))
#  {"real":2.72,"imaginary":3.14}
c1 = JSON3.read(j, JCX)
#  JCX(2.72, 3.14)
3 Likes