diff --git a/src/InPartS.jl b/src/InPartS.jl
index 698bb3e4e4038df711d58fddf6c8a13dfddc2410..47956dcfb5e12ba8dd22d7440645ac69469682e3 100644
--- a/src/InPartS.jl
+++ b/src/InPartS.jl
@@ -116,6 +116,7 @@ include("callbacks.jl")
 include("evolve.jl")
 
 # IO
+include("export/constants.jl")
 include("export/generated.jl")
 include("export/legacy.jl")
 include("export/generic.jl")
diff --git a/src/export/constants.jl b/src/export/constants.jl
new file mode 100644
index 0000000000000000000000000000000000000000..6fdcc8011a2452561433afc49e463fde8977e14a
--- /dev/null
+++ b/src/export/constants.jl
@@ -0,0 +1,27 @@
+
+const PTTYPENAME = "pttype"
+const PMTYPENAME = "pmtype"
+const PTCTYPENAME = "ptctype"
+const OBSTACLEGROUPNAME = "obstacles"
+const PARAMGROUPNAME = "params"
+const REGNAME = "registrar"
+const RNGNAME = "rng"
+const SNAPGROUPNAME = "snapshots"
+const SNAPTIMENAME = "time"
+const SNAPSTEPNAME = "step"
+const SNAPRTSNAME = "writetime"
+const SNAPNEXTIDNAME = "next_id"
+const STRUCTTYPE = "_type"
+const TSSGROUPNAME = "timestepping"
+const DOMAINGROUPNAME = "domain"
+const DOMAINSIZENAME = "size"
+const DOMAINBCNAME = "boundaries"
+
+const VERSIONNAME = "InPartS"
+const TREEHASHNAME = "treehash"
+
+# Estimated number of snapshots in a file
+# JLD2 will allocate a chunk of memory to fit links to approximately EST_NUM_SNAPSHOT
+# snapshot groups. Speeds up reading files with many entries.
+# Cost is a constant "large" amount of unused space (a few kb) for smaller files.
+const EST_NUM_SNAPSHOTS = 2000
diff --git a/src/export/generated.jl b/src/export/generated.jl
index 545fcd6705d2e66d0f5930594d90b39a3d2ae4cb..8c80d1185aeeceb4a6cc090185efaf391e5c9cc3 100644
--- a/src/export/generated.jl
+++ b/src/export/generated.jl
@@ -222,7 +222,7 @@ This module serves as a namespace for all functions generated by the import/expo
 """
 module ExportUtils
     using InPartS
-    import InPartS:SV, ExportItem, FieldExport, AdditionalExport
+    import InPartS:SV, STRUCTTYPE, ExportItem, FieldExport, AdditionalExport
 
     """
         exportfield(::Type, ::ExportItem)
@@ -254,12 +254,26 @@ module ExportUtils
     importname(::Type, ::FieldExport{S}) where S = S # if you value your life, do NOT overwrite this method!!!!!
     importname(t::Type, a::AdditionalExport) = Symbol(exportname(t, a))
 
+    # 
+    """
+        isserializable(T::Type) → Bool
+        isserializable(item::T) → Bool
+    Indicates whether serialize!/deserialize were defined for the type T or item(s) of type T. The type
+    for which `true` is returned always corresponds to the type that can also be passed to `serialize!`.
+    For `VectorExport`s, this means that the relevant serializable type is `Vector{T}`, not `T` itself.
+    
+    For more information on the export system, consult the InPartS manual.
+    """
+    isserializable(::Type) = false
+    isserializable(::T) where {T} = isserializable(T)
+
     #modname(::Type) = "Main"
     # more defaults!
     """
-        serialize!(group, data::Vector{<:AbstractParticle}; [sim::Simulation])
-        serialize!(group, data::AbstractParams)
-    Serialize the `data` into `group`.
+        serialize!(group, data::T)
+        serialize!(group, data::Vector{T<:AbstractParticle}; sim::Simulation)
+    Serialize the `data` of serializable type T into `group`. For types with `ExportStyle(T) == VectorExport()`
+    (currently only `AbstractParticle`), the second form applies.
 
     For more information on the export system, consult the InPartS manual.
     """
@@ -267,14 +281,39 @@ module ExportUtils
     serialize!(::Any, @nospecialize(a), @nospecialize(s::InPartS.ExportStyle); kwargs...) = error("InPartS: no $s serializer defined for type $(typeof(a))")
 
     """
-        deserialize(group, type::Type{T}; [sim::Simulation]) where {T<:AbstractParticle} → particles::Vector{T}
-        deserialize(group, type::Type{T}) where {T<:AbstractParams} → param::T
-    Deserializes the `group` into either a vector of `type` or a single instance of `type`.
+        serializetodict(data::T) → Dict{String, Any}
+        serializetodict(data::Vector{T<:AbstractParticle}; sim::Simulation) → Dict{String, Any}
+    Serialize the `data` of serializable type `T` into a Dict{String, Any}, including the name of the
+    type `T` itself, which is stored at the key "$(STRUCTTYPE)". For types with `ExportStyle(T) == VectorExport()`
+    (currently only `AbstractParticle`), the second form applies. The result can be deserialzed using
+    [`deserializefromdict`](@ref).
+
+    For more information on the export system, consult the InPartS manual.
+    """
+    serializetodict(d::T; kwargs...) where {T} = serialize!(Dict{String, Any}(STRUCTTYPE => string(T)), d; kwargs...)
+    serializetodict(d::Vector{T}; kwargs...) where {T} = serialize!(Dict{String, Any}(STRUCTTYPE => string(T)), d; kwargs...)
+
+    """
+        deserialize(group, type::Type{T}) → result::T
+        deserialize(group, type::Type{T<:AbstractParticle}; sim::Simulation) → particles::Vector{T}
+    Deserializes the `group` into either a single instance of `type` or a vector of `type`. For types with
+    `ExportStyle(T) == VectorExport()` (currently only `AbstractParticle`), the latter, and thus, the second form, applies.
+
+    For more information on the export system, consult the InPartS manual.
+    """
+    deserialize(::Any, @nospecialize(a::Type); kwargs...) = error("InPartS: no deserializer defined for type $a")
+
+    """
+        deserializefromdict(dict, stype = Any; [sim::Simulation]) → Vector{T} where {T<:AbstractParticle}
+        deserializefromdict(dict, stype = Any) → T
+    The reverse operation of [`serializetodict`](@ref). Deserializes the `dict` into either a vector or
+    a single instance of the type T saved in `dict["$(STRUCTTYPE)"]`, depending on whether `T`
+    The optional `stype` argument is checked to be `T`'s supertype during reconstruction.
 
     For more information on the export system, consult the InPartS manual.
     """
-    deserialize(g, t; kwargs...) = serialize!(g, d, InPartS.ExportStyle(t); kwags...)
-    deserialize(::Any, @nospecialize(a::Type), @nospecialize(s::InPartS.ExportStyle); kwargs...) = error("InPartS: no $s deserializer defined for type $a")
+    deserializefromdict(dict, stype = Any; kwargs...) = deserialize(dict, InPartS.reconstruct_subtype(dict[STRUCTTYPE], stype); kwargs...)
+
 end
 
 """
@@ -326,7 +365,7 @@ of `type` is itself an InPartS-exportable type (produced, e.g., with @genexport)
 
 This macro produces code
 ```julia
-@exporttransform {type} {fieldname} ExportUtils.serialize!(Dict{String, Any}("_type" => string(typeof(value))), value) ExportUtils.deserialize(data, InPartS.reconstruct_subtype(data["_type"], {fieldtype}))
+@exporttransform {type} {fieldname} ExportUtils.serializetodict(value) ExportUtils.deserializefromdict(data, {fieldtype})
 ```
 where `fieldtype` is the automatically determined type of the field.
 """
@@ -334,7 +373,7 @@ macro exportrecursive(type, field::Symbol)
     check_type(type)
     local ft = fieldtype(__module__.eval(type), field)
     return quote
-        @exporttransform $(esc(type)) $(field) ExportUtils.serialize!(Dict{String, Any}("_type" => string(typeof(value))), value) ExportUtils.deserialize(data, InPartS.reconstruct_subtype(data["_type"], $(ft)))
+        @exporttransform $(esc(type)) $(field) ExportUtils.serializetodict(value) ExportUtils.deserializefromdict(data, $(ft))
     end
 end
 
@@ -437,17 +476,12 @@ end
     @genexport(type<:AbstractParticle)
 Generates the import/export functions for the given particle and its [`InPartS.ParamType`](@ref).
 
-!!! warning
-
-    This macro evals generated code into `InPartS.ExportUtils`. Using it as a top-level
-    statement in a precompiled package will bring naught but pain and destruction.
-
-    If you want to automatically generate export/import functions when loading a package,
-    consider putting `@genexport` in the [`__init__()`](@ref) function.
+For more information on the export system, consult the InPartS manual.
 """
 macro genexport(type::Union{Symbol,Expr})
     return quote
         local mod = @__MODULE__
+        Base.eval(mod, _isserializable_function($(esc(type))))
         Base.eval(mod, _serialize_function($(esc(type))))
         Base.eval(mod, _deserialize_function($(esc(type))))
         #ExportUtils.modname(::Type{mod.$type}) = "$mod"
@@ -455,6 +489,22 @@ macro genexport(type::Union{Symbol,Expr})
 end
 
 
+## isserializable
+"""
+    _isserializable_function(T)
+Returns an expression containing the definition of [`ExportUtils.isserializable()`](@ref)
+for the given type `T`.
+"""
+_isserializable_function(T::Type) = _isserializable_function(T, ExportStyle(T))
+
+_isserializable_function(T::Type, ::ScalarExport) = quote
+    ExportUtils.isserializable(::Type{<:$T}) = true
+end
+
+_isserializable_function(T::Type, ::VectorExport) = quote
+    ExportUtils.isserializable(::Type{Vector{ET}}) where {ET<:$T} = true
+end
+
 
 ## Serialization
 
diff --git a/src/export/generic.jl b/src/export/generic.jl
index 7e0af3929c6349d7c9a51fa43bf685e43156a14d..3d6432c9d25f1c8c837a5c6d8830621e3be660d0 100644
--- a/src/export/generic.jl
+++ b/src/export/generic.jl
@@ -1,32 +1,5 @@
 export SaveCallback, BackupCallback, readsim
 
-const PTTYPENAME = "pttype"
-const PMTYPENAME = "pmtype"
-const PTCTYPENAME = "ptctype"
-const OBSTACLEGROUPNAME = "obstacles"
-const PARAMGROUPNAME = "params"
-const REGNAME = "registrar"
-const RNGNAME = "rng"
-const SNAPGROUPNAME = "snapshots"
-const SNAPTIMENAME = "time"
-const SNAPSTEPNAME = "step"
-const SNAPRTSNAME = "writetime"
-const SNAPNEXTIDNAME = "next_id"
-const STRUCTTYPE = "_type"
-const TSSGROUPNAME = "timestepping"
-const DOMAINGROUPNAME = "domain"
-const DOMAINSIZENAME = "size"
-const DOMAINBCNAME = "boundaries"
-
-const VERSIONNAME = "InPartS"
-const TREEHASHNAME = "treehash"
-
-# Estimated number of snapshots in a file
-# JLD2 will allocate a chunk of memory to fit links to approximately EST_NUM_SNAPSHOT
-# snapshot groups. Speeds up reading files with many entries.
-# Cost is a constant "large" amount of unused space (a few kb) for smaller files.
-const EST_NUM_SNAPSHOTS = 2000
-
 ## Datafile interface definitions
 """
     dfopen(backend, filename; kwargs...)