|There's more than one way to do things|
You have explained what he is trying to accomplish, but I actually more was looking to know why he wants to do this. This kind of access control is, in my opinion, a social issue that should not be solved with technology. But since I actually don't know the motivation, I might be wrong on that.
Replace fields with another object system that detect access to a non-declared object variable (run-time or compile-time).
This being perl, there is always a way to access and manipulate object fields no matter what technique you use (yes, even Inside-Out objects can be reached with the right level of black magic). So no matter what features I show you, there is always a way around it for someone who is motivated enough.
Is there an perl object system / framework that supports the above requirements? Moose and Mouse have been considered and fail the last requirement.
Moose has read-only accessors and if you pass unknown attributes to the constructor they will not be added to the underlying object instance. There is also the MooseX::StrictConstructor module which makes passing those unknown attributes an error. Honestly, this, along with some tight type constraints should be enough to validate the data coming from JSON::XS and being passed to the Moose constructor, which seems to be what your goal is.
That all said, none of this will protect the underlying instance structure. But I really think you need to ask yourself, is my environment so hostile that I actually need to protect the instance structure? Can I not enforce things via the accessors and object interface?
Now, if you are really dead set on actually doing this, Moose does provide you the features you need (I suspect Mouse can do this too, but I don't know the actual incantations to do so). So here is the code that you would need in your class BUILD method (or in a common base class if you want, it will still work correctly).
This basically just locks the instance, then locks each of the values that are defined via Moose. This means that when you create an instance with Moose like so...
You will first get the normal Moose constructor behavior which is to only use the keys that are defined. But since I actually added MooseX::StrictConstructor into this, that means that if you were to do this ...
The code would fail with an exception.
Next, since we locked all the hash keys, if someone were to try and do this ..
It would die with the error "Attempt to access disallowed key 'baz' in a restricted hash" which is because of Hash::Util::lock_keys.
And lastly, since lock_keys only prevents you from adding new keys but not altering existing ones, we found all the valid keys in Moose and locked those values with Hash::Util::lock_values, which means that this code ...
Will throw the following error "Modification of a read-only value attempted".
Now, in my opinion, all this is overkill. If the users of your class are violating the encapsulation, then you have a real problem with those users and your documentation should point out such incorrect usage. If these other users are actually co-workers, then you have a social problem on your hands and one that needs to be fixed socially and not technically. In the end, all some jerk needs to do is use Hash::Util to unlock the keys/values and they can get at that, but this really is an explicit violation and should be dealt with.