Perl variables are multivalued, e.g. they have slots for string, integer, floating point and reference.
If you assign a string to a variable it will be stored in the string slot and this slot will be marked as valid. If you later access the variable as a number, the string will be converted and the floating point or the integer slots will be updated and marked valid. In your example $str will initially have a valid string slot and after the comparison with an integer it will also have valid integer slot. If you would have compared it with a float it would have a valid float slot instead. You might check this with Devel::Peek::Dump: POK means valid string slot, IOK is a valid integer slot and NOK a valid float slot.
Similar things happen, if you store an integer and later use it as a string. In your example $num will have initially a valid integer slot (IOK) and once you access it as a string (by using eq) it will be converted to a string and the string slot will be filled and be valid (POK) additionally to the integer slot.
to_json probably just looks at the variable and takes the first valid slot it finds, starting with the string (I guess this must be encode_sv in JSON::XS - this checks in this order: string, float, integer). So if the string slot is valid it will be printed with quotes around it.
Other languages (like python) don't have this multi-slot thing, they just have a single type and if you use the variable in another context it will croak. Both ways have their pros and cons :)