EDIT, much better way to do this using properties
https://blender.stackexchange.com/a/147144/15543
https://blender.stackexchange.com/a/197072/15543
Fudge the driver system 2.9x
I removed the previous answer re doing this in 2.8, don't really recommend this one..
Melodicpinpon asks
Would it work in 2.9 version? This question has been debated in many
places and this method could be the solution...

Using a method somewhat similar to @AtomicBezierSlinger can add a driver to every object in the scene that has a parent.
However the use of use self makes this driver readily copy pastable into other objects, without the need to check or set the parent object.
Here is a test script to add the driver to all objects with a parent.
import bpy
from bpy.app import driver_namespace as dns
def test(self, dg):
vl = dg.view_layer
return not self.parent.original.visible_get(view_layer=vl)
dns["test"] = test
context = bpy.context
scene = context.scene
#parented objects
parented = [o for o in scene.objects if o.parent]
for o in parented:
#o.animation_data_clear() # testing
#continue
o.animation_data_create()
fcurve = o.driver_add("hide_viewport")
driver = fcurve.driver
driver.expression = "test(self, depsgraph)"
driver.use_self = True
# dummy var to make driver update.
var = driver.variables.new()
var.name = "dummy" # frame
var.targets[0].id_type = 'SCENE'
var.targets[0].id = scene
var.targets[0].data_path = "frame_current"
Making a test method and adding to the driver namespace is unnecessary as it can be directly crunched into expression field as
not self.parent.original.visible_get(view_layer=depsgraph.view_layer)
use self is not default and will need to be set.
answer below is for versions prior 2.8 with the use self option available. Also not a recommended way to use driver system.
Fudge the driver system
Firstly we'll set up a custom driver that sets a property of all children recursively to the value of the parent.
import bpy
def traverse_tree(t):
yield t
for child in t.children:
yield from traverse_tree(child)
def set_children(obj, prop):
val = getattr(obj, prop, None)
if val is not None:
for c in traverse_tree(obj):
setattr(c, prop, val)
return val
bpy.app.driver_namespace["setchildprops"] = set_children
Now we can add a driver to a custom property on the same object as the property we wish to set on all children.
setchildprops(self, "hide")
making sure to check the non default "use_self" property of the driver.

Now if you keyframe the "hide" property of the same object it will hide / show children accordingly. Note it will not update the UI automatically, until some property eg frame is updated.