You are right in your assumptions, in a way.
It must be possible to partition the set of all objects in the program into groups
1) You have complete information which allows complete deconstruction and reconstruction of the object. Arrays of numbers or strings, structs are good examples.
2) You have construction information. You can reconstruct the object by calling external code. A file is a good example, but it requires that your program has a file abstraction that remembers the construction and state parameters. We can for example save the path to the file and the position in the file. However reconstruction might fail. (For example, the file was deleted or changed)
3) You have no construction information, the object was somehow randomly received.
Here, to be able to serialize the objects completely, we have to go from 3) to 2) to 1). Objects in 3) can be attributes of an object of type 2), and can be retrieved by successfully reconstructing a type 2) object.
A type 2) object however, must be reconstructed by serializing just construction information, which has to be of type 1), for example numbers and strings, true data.
This whole scheme seems costly since if we want to reconstruct the whole program state, we have to work with abstractions that encapsulate objects of type 2). And we have to know what we do when an object cannot be reconstructed. Also, we must be sure that we don't mix objects of these types, that we don't mix in objects of type 3 or 2 where we expect to collect just objects of type 1.