quinnj / JSON3.jl

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Struct map a `JSON3.Object` to a struct?

DilumAluthge opened this issue · comments

Suppose that I read some JSON in like this:

json_object = JSON3.read(my_json_string) # `json_object` is of type `JSON3.Object`

Can I now convert json_object to a struct of type T, where I have done the necessary work to set up struct mapping for T?

Obviously, I can do this with:

json_object = JSON3.read(my_json_string) # `json_object` is of type `JSON3.Object`
temp_string = JSON3.write(json_object) # `temp_string` is a `String
my_object = JSON3.read(temp_string, T) # now `my_object` is of type `T`

But is there a way to do this that doesn't require me to write it out to an intermediate string? I guess I'm looking for a method struct_map such that this works:

json_object = JSON3.read(my_json_string)
my_object = JSON3.struct_map(json_object, T) # I want `my_object` to be of type `T`

For what it's worth, in my use case, my type T will always be of the StructTypes.Mutable() struct type. Not sure if that's helpful or not.

Can you explain why you can't do json_object = JSON3.read(my_json_string, T) in your initial read? But in any case, I agree this could be useful; I actually had a use-case for this last night working on wrapping a web api where their documented json model was horribly out of date, so I turned my julia structs into essentially:

mutable struct Obj
    id::Any
    prop1::Any
    prop2::Any
    Obj() = new()
end

I ended up with code like:

makeobj(::Type{<:Union{Real, String}}, obj) = obj
makeobj(::Type{Any}, obj) = obj
makeobj(::Type{T}, obj) where {T <: NamedTuple} = NamedTuple(Tuple(k => makeobj(fieldtype(T, k), v) for (k, v) in obj))

function makeobj(::Type{T}, obj) where {T <: AbstractVector}
    x = similar(T, 0)
    for y in obj
        push!(x, makeobj(eltype(T), y))
    end
    return x
end

function makeobj(::Type{T}, obj) where {T}
    x = T()
    for (k, v) in obj
        if hasfield(T, k)
            setfield!(x, k, makeobj(fieldtype(T, k), v))
        end
    end
    return x
end

I think what might make the most sense here is to actually add some code like this to the StructTypes.jl package, that takes any AbstractDict and a T and tries to construct an instance of T from the key-value pairs in the AbstractDict. Then it would "just work" with JSON3.Object.

Can you explain why you can't do json_object = JSON3.read(my_json_string, T) in your initial read?

Yeah... I don't know ahead of time what kind of object I will get. It's similar to the use case I was talking about on Slack. Except in the Slack use case, I had a field name that told me which type I would get, so I could use the "abstract type" struct type. In the use case that motivated this issue, I don't even have that. I can find out later which type it is, and thus convert the JSON object to the appropriate type, but it takes some work and time to figure out what the type is.

I think what might make the most sense here is to actually add some code like this to the StructTypes.jl package, that takes any AbstractDict and a T and tries to construct an instance of T from the key-value pairs in the AbstractDict. Then it would "just work" with JSON3.Object.

This sounds great!