Converting String to Datatype with Meta.parse

I’ve made a package to create JSONPointer from a string, then construct the Dictionary from JSONPointer data.

But I’ve added extra functionality to define static type for JSONPointer.
here is example

pkg>add XLSXasJSON

using XLSXasJSON
import XLSXasJSON.JSONPointer

p = JSONPointer("/a::Int")
d = Dict(p)
d[p] = "a" # throws assert 

Which throws assert as I intended but this doesn’t work

struct Foo 
end
p = JSONPointer("/a::Foo")
d = Dict(p) # throws UndefVarError

Is there a way I can make this work for user-defined type?

Your problem lies probably in the use of eval rather than Meta.parse (which, incidentally, you could probably replace by Symbol in this case). The problem is that eval evaluates an expression within the context of a module. If everything was defined in the same module, you wouldn’t have any problem:

julia> struct Foo end

julia> gettype(s::String) = Meta.parse(s) |> eval
gettype (generic function with 1 method)

julia> gettype("Int")
Int64

julia> gettype("Foo")
Foo

In your case however, since your JSONPointer constructor is in a separate package (and therefore a separate module), the type symbol is evalled in the context of the JSONPointer module, where type Foo does not exist. Hence the error:

julia> module MyModule
       gettype(s::String) = Meta.parse(s) |> eval
       end
Main.MyModule

# Still works because `Int` is defined both in Main and MyModule
# (and refers to the same thing, which could not be the case!)
julia> MyModule.gettype("Int")
Int64

# Does not work because Foo is unknown in the context of MyModule
julia> MyModule.gettype("Foo")
ERROR: UndefVarError: Foo not defined

One way you could circumvent the problem would be to add an argument providing the module in which type names have to be evaluated:

julia> module MyModule2
       gettype(s::String, mod=@__MODULE__) = Symbol(s) |> mod.eval
       end
Main.MyModule2

# No absolute need to provide a module here
# (but it would probably be better anyway)
julia> MyModule2.gettype("Int")
Int64

# Same as above
julia> MyModule2.gettype("Foo")
ERROR: UndefVarError: Foo not defined

# Foo is known in the context of the calling module
julia> MyModule2.gettype("Foo", @__MODULE__)
Foo


For this type of reasons, using eval is sometimes considered “bad style”. I personally don’t know any way avoiding it in this case; hopefully someone more knowledgeable will chime in and propose a better solution…

1 Like

Thank you for clarifying this :grinning:
I will have to come up with an interface to provide the module name to XLSXasJSON.

Providing a module is most likely not the correct solution.

The fundamental issue is that the a name is not enough to uniquely qualify an object (including types). A name needs to be combined with a name space to look up into.

Unless you have some specific use case that will always require the user to import all the needed types into the same namespace (which is basically not needed for anything else in the language) and unless the string is not meant to transfer generic information, you should not pass in the module separately.

If the string is used basically as code to pass local information, then you should not use string. You should pass in the information directly. If you are only using it to construct an object more easily, you can use a macro.

Otherwise you should encode the fully qualified name in the string.

1 Like

Could you please elaborate on what a “name + namespace” combination is, if it is not a (name, module) pair?

2 Likes

It’s a name module pair.

If you are asking what’s the difference between what I said and what you are suggesting, I’m saying that what’s missing is not a module for the function, it’s missing from the string.

Also something that I forgot to say, this is the simplest case to avoid eval, you are just accessing module member and you should just do that instead of eval. What you need is getfield or getproperty.

3 Likes

Thanks, it’s clearer that way.


Of course, that makes sense:

julia> struct Foo end
julia> getfield(@__MODULE__, Symbol("Foo"))
Foo
1 Like

hmm… But getfield doesn’t work for parametric type

julia> getfield(Main, Symbol("Union{Int, Missing}"))
julia> getfield(Main, Symbol("Array{Int, 1}"))

I am not on computer but I think what you want for parametric is:

uatype =  getfield(Main, :Array
typeparam = getfield(Main, :Int}
uatype{typeparam, 1}

You could use Meta.parse and then a little bit of logic to do this.

Its a question as to what module you should be using.
To be preperly explict about the type in this case you should be using its originating module-- Base.
But if you want what ever is in scope then @__MODULE__
or if in repl and saving chacacters Main

1 Like