EEPROM: managing address mapping between firmware versions
There's lots of ways you could handle this.

Two thoughts right off the top of my head:


Have a "Version" identifier in the EEPROM.  


The firmware then needs to know the EEPROM layout of all versions up to the current one. It can then read the EEPROM from the old version and write back a new format for the current version.


Have each entry in the EEPROM tagged with a "meaning".


This is not too different to your JSON idea (which you are right is horrible), but instead of lots of words you just use a simple number to identify the meaning of the value it is associated with.  Assuming that each stored value is an 8-bit byte (though it doesn't have to be), you could allocate "1" as "set_temperature", "2" as "temperature_hysteresis", "3" as "pid_gains", etc, Then the EEPROM might contain the bytes 1,96,2,5 and the firmware would know that the second byte is the set_temperature because it has a 1 in the first byte.

You can think of it as a "key / value" pair, but both the key and the value are just numbers.

To find the temperature_hysteresis you just scan through all the even EEPROM entries until you find 2, then read the next entry. If you don't find a 2 then you use the default value.

Your firmware can then choose to either update an existing cell that is tagged with the right type (scan for type, update next cell), or it could erase all the values and replace them with just the ones it cares about - the choice is yours.

Any values that your firmware no longer cares about will be ignored, and potentially erased.