JSON3 read `Dict{Tuple{Int,Int}, T}` unexpected behavior

Hey,

I’m using JSON3.jl in a project and I found this unexpected behavior when reading Dict{Tuple{Int,Int}, T}.
I don’t know if I’m doing something wrong or if there is just a bug in the package but this is a quite classic operation so I thought it would work straight away.

Here is a simple example that reproduces the bug :

using StructTypes, JSON3

struct A
  a::Dict{Tuple{Int,Int}, Char}
end

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

a = A(Dict((1, 2) => 'a', (2, 4) => 'b', (15, 20) => 'c'))

@show JSON3.read(JSON3.write(a), A)

The write operation works well. You get this JSON:

"{\"a\":{\"(2, 4)\":\"b\",\"(1, 2)\":\"a\",\"(15, 20)\":\"c\"}}"

However, when you read the output of write you get the following result:

A(Dict((40, 50) => 'b', (40, 49) => 'c'))

I’m pretty sure nobody would expect this behavior.
Has anyone experienced this problem and know how to fix it?

Best,

Ok I think I found the root of the problem:

julia> JSON3.write((1, 2))
"[1,2]"

I’ll edit this answer if I find a solution :

function parsetuple(s::AbstractString)
    r = match(r"\(([0-9]+), *([0-9]+)\)", s)
    return (parse(Int, r.captures[1]), parse(Int, r.captures[2]))
end
StructTypes.StructType(::Type{Tuple{Int,Int}}) = StructTypes.StringType()
StructTypes.construct(::Type{Tuple{Int,Int}}, x::String; kw...) = parsetuple(x)

trick if you’re using JSON3 just for debugging purposes.
It should no go in prod as StructTypes.StructType(::Type{Tuple{Int,Int}}) is already defined and returns ArrayType().

or define a new constructor and convert the string keys into the desired type.

I think the main problem is that JSON keys must be strings, so it is writing (1,2) as a string "(1,2)" to use as the key, which does not preserve the actual meaning of the tuple.

I think it is a bad bug that you get

A(Dict((40, 50) => 'b', (40, 49) => 'c'))

back, it should error instead.

edit: filed `JSON3.read(str, MyStruct)` does not roundtrip correctly nor error · Issue #285 · quinnj/JSON3.jl · GitHub

1 Like

There is a similar issue about using Structs as keys in the package already : Reading and writing is broken for `Dict`s with struct keys · Issue #71 · quinnj/JSON3.jl · GitHub.
JSON only allows string keys unfortunately.

2 Likes

Hey,

A solution to my problem (I think much better than the quick workaround I wrote last week)

using JSON3, StructTypes

struct A
    edges::Dict{Tuple{Int,Int}, String}
end

StructTypes.StructType(::Type{A}) = StructTypes.CustomStruct()

struct _JSONEdge
    src::Int
    dst::Int
    label::String
end

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

struct _JSONA
    edges::Vector{_JSONEdge}
end

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

_JSONA(a::A) = _JSONA([_JSONEdge(src, dst, label) for ((src, dst), label) in a.edges])

StructTypes.lowertype(::Type{A}) = _JSONA
StructTypes.lower(a::A) = _JSONA(a)

A(json::_JSONA) = A(Dict((edge.src, edge.dst) => edge.label for edge in json.edges))

a = A(Dict((1,2) => "a", (2,3) => "b"))
json = JSON3.write(a)
b = JSON3.read(json, A)