diff --git a/odml/base.py b/odml/base.py
index e145c8c..3940880 100644
--- a/odml/base.py
+++ b/odml/base.py
@@ -84,34 +84,18 @@ class baseobject(_baseobj):
         return id(self)
 
 
-class SafeList(list):
+class SmartList(list):
 
-    def index(self, obj):
+    def __init__(self, content_type):
         """
-        Find obj in list
-
-        Be sure to use "is" based comparison (instead of __eq__)
+        Only values of the instance *content_type* can be added to the SmartList.
         """
-        for i, e in enumerate(self):
-            if e is obj:
-                return i
-        raise ValueError("remove: %s not in list" % repr(obj))
-
-    def remove(self, obj):
-        """
-        Remove an element from this list.
-
-        Be sure to use "is" based comparison (instead of __eq__)
-        """
-        del self[self.index(obj)]
-
-
-class SmartList(SafeList):
+        self._content_type = content_type
+        super(SmartList, self).__init__()
 
     def __getitem__(self, key):
         """
-        Provides element index also by searching for an element with a given
-        name
+        Provides element index also by searching for an element with a given name.
         """
         # Try normal list index first (for integers)
         if isinstance(key, int):
@@ -125,31 +109,68 @@ class SmartList(SafeList):
         # and fail eventually
         raise KeyError(key)
 
+    def __setitem__(self, key, value):
+        """
+        Replaces item at list[*key*] with *value*.
+        :param key: index position
+        :param value: object that replaces item at *key* position.
+                      value has to be of the same content type as the list.
+                      In this context usually a Section or a Property.
+        """
+        if not isinstance(value, self._content_type):
+            raise ValueError("List only supports elements of type '%s'" %
+                             self._content_type)
+
+        # If required remove new object from its old parents child-list
+        if hasattr(value, "_parent") and (value._parent and value in value._parent):
+            value._parent.remove(value)
+
+        # If required move parent reference from replaced to new object
+        # and set parent reference on replaced object None.
+        if hasattr(self[key], "_parent"):
+            value._parent = self[key]._parent
+            self[key]._parent = None
+
+        super(SmartList, self).__setitem__(key, value)
+
     def __contains__(self, key):
         for obj in self:
             if (hasattr(obj, "name") and obj.name == key) or key == obj:
                 return True
 
+    def index(self, obj):
+        """
+        Find obj in list
+        """
+        for i, e in enumerate(self):
+            if e is obj:
+                return i
+        raise ValueError("remove: %s not in list" % repr(obj))
+
+    def remove(self, obj):
+        """
+        Remove an element from this list.
+        """
+        del self[self.index(obj)]
+
     def append(self, *obj_tuple):
-        from odml.section import BaseSection
-        from odml.doc import BaseDocument
         for obj in obj_tuple:
             if obj.name in self:
                 raise KeyError(
                     "Object with the same name already exists! " + str(obj))
 
-            if (not isinstance(obj, BaseSection)) & \
-               isinstance(self, BaseDocument):
-                raise KeyError("Object " + str(obj) + " is not a Section.")
+            if not isinstance(obj, self._content_type):
+                raise ValueError("List only supports elements of type '%s'" %
+                                 self._content_type)
 
             super(SmartList, self).append(obj)
 
 
 @allow_inherit_docstring
 class sectionable(baseobject):
-
     def __init__(self):
-        self._sections = SmartList()
+        from odml.section import Section
+        self._sections = SmartList(Section)
         self._repository = None
 
     @property
@@ -518,9 +539,10 @@ class sectionable(baseobject):
         Clone this object recursively allowing to copy it independently
         to another document
         """
+        from odml.section import Section
         obj = super(sectionable, self).clone(children)
         obj._parent = None
-        obj._sections = SmartList()
+        obj._sections = SmartList(Section)
         if children:
             for s in self._sections:
                 obj.append(s.clone())
diff --git a/odml/property.py b/odml/property.py
index 894296d..a23340b 100644
--- a/odml/property.py
+++ b/odml/property.py
@@ -373,45 +373,83 @@ class BaseProperty(base.baseobject, Property):
 
         return obj
 
-    def merge(self, other, strict=True):
+    def merge_check(self, source, strict=True):
         """
-        Merges the property 'other' into self, if possible. Information
-        will be synchronized. Method will raise a ValueError when the
-        information in this property and the passed property are in
-        conflict.
+        Checks whether a source Property can be merged with self as destination and
+        raises a ValueError if the values of source and destination are not compatible.
+        With parameter *strict=True* a ValueError is also raised, if any of the
+        attributes unit, definition, uncertainty, reference or value_origin and dtype
+        differ in source and destination.
 
-        :param other: an odML Property.
-        :param strict: Bool value to indicate whether types should be implicitly converted
-               even when information may be lost. Default is True, i.e. no conversion,
-               and a ValueError will be raised if types do not match.
+        :param source: an odML Property.
+        :param strict: If True, the attributes dtype, unit, uncertainty, definition,
+                       reference and value_origin of source and destination
+                       must be identical.
         """
-        assert(isinstance(other, BaseProperty))
-        if strict and self.dtype != other.dtype:
+        if not isinstance(source, BaseProperty):
+            raise ValueError("odml.Property.merge: odML Property required.")
+
+        # Catch unmerge-able values at this point to avoid
+        # failing Section tree merges which cannot easily be rolled back.
+        new_value = self._convert_value_input(source.value)
+        if not self._validate_values(new_value):
+            raise ValueError("odml.Property.merge: passed value(s) cannot "
+                             "be converted to data type '%s'!" % self._dtype)
+        if not strict:
+            return
+
+        if (self.dtype is not None and source.dtype is not None and
+                self.dtype != source.dtype):
             raise ValueError("odml.Property.merge: src and dest dtypes do not match!")
 
-        if self.unit is not None and other.unit is not None and self.unit != other.unit:
-            raise ValueError("odml.Property.merge: src and dest units (%s, %s) do not match!" % (other.unit, self.unit))
+        if self.unit is not None and source.unit is not None and self.unit != source.unit:
+            raise ValueError("odml.Property.merge: "
+                             "src and dest units (%s, %s) do not match!" %
+                             (source.unit, self.unit))
 
-        if self.definition is not None and other.definition is not None:
+        if (self.uncertainty is not None and source.uncertainty is not None and
+                self.uncertainty != source.uncertainty):
+            raise ValueError("odml.Property.merge: "
+                             "src and dest uncertainty both set and do not match!")
+
+        if self.definition is not None and source.definition is not None:
             self_def = ''.join(map(str.strip, self.definition.split())).lower()
-            other_def = ''.join(map(str.strip, other.definition.split())).lower()
+            other_def = ''.join(map(str.strip, source.definition.split())).lower()
             if self_def != other_def:
-                raise ValueError("odml.Property.merge: src and dest definitions do not match!")
-
-        if self.uncertainty is not None and other.uncertainty is not None:
-            raise ValueError("odml.Property.merge: src and dest uncertainty both set and do not match!")
+                raise ValueError("odml.Property.merge: "
+                                 "src and dest definitions do not match!")
 
-        if self.reference is not None and other.reference is not None:
+        if self.reference is not None and source.reference is not None:
             self_ref = ''.join(map(str.strip, self.reference.lower().split()))
-            other_ref = ''.join(map(str.strip, other.reference.lower().split()))
+            other_ref = ''.join(map(str.strip, source.reference.lower().split()))
             if self_ref != other_ref:
-                raise ValueError("odml.Property.merge: src and dest references are in conflict!")
+                raise ValueError("odml.Property.merge: "
+                                 "src and dest references are in conflict!")
 
-        if self.value_origin is not None and other.value_origin is not None:
+        if self.value_origin is not None and source.value_origin is not None:
             self_ori = ''.join(map(str.strip, self.value_origin.lower().split()))
-            other_ori = ''.join(map(str.strip, other.value_origin.lower().split()))
+            other_ori = ''.join(map(str.strip, source.value_origin.lower().split()))
             if self_ori != other_ori:
-                raise ValueError("odml.Property.merge: src and dest value_origin are in conflict!")
+                raise ValueError("odml.Property.merge: "
+                                 "src and dest value_origin are in conflict!")
+
+    def merge(self, other, strict=True):
+        """
+        Merges the Property 'other' into self, if possible. Information
+        will be synchronized. By default the method will raise a ValueError when the
+        information in this property and the passed property are in conflict.
+
+        :param other: an odML Property.
+        :param strict: Bool value to indicate whether types should be implicitly converted
+               even when information may be lost. Default is True, i.e. no conversion,
+               and a ValueError will be raised if types or other attributes do not match.
+               If a conflict arises with strict=False, the attribute value of self will
+               be kept, while the attribute value of other will be lost.
+        """
+        if not isinstance(other, BaseProperty):
+            raise TypeError("odml.Property.merge: odml Property required.")
+
+        self.merge_check(other, strict)
 
         if self.value_origin is None and other.value_origin is not None:
             self.value_origin = other.value_origin
diff --git a/odml/section.py b/odml/section.py
index 392e2af..4c32a32 100644
--- a/odml/section.py
+++ b/odml/section.py
@@ -7,7 +7,7 @@ from . import format
 from . import terminology
 from .doc import BaseDocument
 # this is supposedly ok, as we only use it for an isinstance check
-from .property import Property
+from .property import BaseProperty
 # it MUST however not be used to create any Property objects
 from .tools.doc_inherit import inherit_docstring, allow_inherit_docstring
 
@@ -35,7 +35,7 @@ class BaseSection(base.sectionable, Section):
 
         # Sets _sections Smartlist and _repository to None, so run first.
         super(BaseSection, self).__init__()
-        self._props = base.SmartList()
+        self._props = base.SmartList(BaseProperty)
 
         try:
             if id is not None:
@@ -173,8 +173,10 @@ class BaseSection(base.sectionable, Section):
             return None
 
     @definition.setter
-    def definition(self, val):
-        self._definition = val
+    def definition(self, new_value):
+        if new_value == "":
+            new_value = None
+        self._definition = new_value
 
     @definition.deleter
     def definition(self):
@@ -186,6 +188,8 @@ class BaseSection(base.sectionable, Section):
 
     @reference.setter
     def reference(self, new_value):
+        if new_value == "":
+            new_value = None
         self._reference = new_value
 
     # API (public)
@@ -266,10 +270,10 @@ class BaseSection(base.sectionable, Section):
 
         :param obj: Section or Property object.
         """
-        if isinstance(obj, Section):
+        if isinstance(obj, BaseSection):
             self._sections.append(obj)
             obj._parent = self
-        elif isinstance(obj, Property):
+        elif isinstance(obj, BaseProperty):
             self._props.append(obj)
             obj._parent = self
         elif isinstance(obj, collections.Iterable) and not isinstance(obj, str):
@@ -291,15 +295,15 @@ class BaseSection(base.sectionable, Section):
 
         # Make sure only Sections and Properties with unique names will be added.
         for obj in obj_list:
-            if not isinstance(obj, Section) and not isinstance(obj, Property):
+            if not isinstance(obj, BaseSection) and not isinstance(obj, BaseProperty):
                 raise ValueError("odml.Section.extend: "
                                  "Can only extend sections and properties.")
 
-            elif isinstance(obj, Section) and obj.name in self.sections:
+            elif isinstance(obj, BaseSection) and obj.name in self.sections:
                 raise KeyError("odml.Section.extend: "
                                "Section with name '%s' already exists." % obj.name)
 
-            elif isinstance(obj, Property) and obj.name in self.properties:
+            elif isinstance(obj, BaseProperty) and obj.name in self.properties:
                 raise KeyError("odml.Section.extend: "
                                "Property with name '%s' already exists." % obj.name)
 
@@ -315,14 +319,14 @@ class BaseSection(base.sectionable, Section):
         :param position: index at which the object should be inserted.
         :param obj: Section or Property object.
         """
-        if isinstance(obj, Section):
+        if isinstance(obj, BaseSection):
             if obj.name in self.sections:
                 raise ValueError("odml.Section.insert: "
                                  "Section with name '%s' already exists." % obj.name)
 
             self._sections.insert(position, obj)
             obj._parent = self
-        elif isinstance(obj, Property):
+        elif isinstance(obj, BaseProperty):
             if obj.name in self.properties:
                 raise ValueError("odml.Section.insert: "
                                  "Property with name '%s' already exists." % obj.name)
@@ -341,10 +345,10 @@ class BaseSection(base.sectionable, Section):
 
         :param obj: Section or Property object.
         """
-        if isinstance(obj, Section):
+        if isinstance(obj, BaseSection):
             self._sections.remove(obj)
             obj._parent = None
-        elif isinstance(obj, Property):
+        elif isinstance(obj, BaseProperty):
             self._props.remove(obj)
             obj._parent = None
         else:
@@ -373,7 +377,7 @@ class BaseSection(base.sectionable, Section):
         obj = super(BaseSection, self).clone(children)
         obj._id = str(uuid.uuid4())
 
-        obj._props = base.SmartList()
+        obj._props = base.SmartList(BaseProperty)
         if children:
             for p in self._props:
                 obj.append(p.clone())
@@ -388,10 +392,10 @@ class BaseSection(base.sectionable, Section):
 
         :param obj: Section or Property object.
         """
-        if isinstance(obj, Section):
+        if isinstance(obj, BaseSection):
             return super(BaseSection, self).contains(obj)
 
-        elif isinstance(obj, Property):
+        elif isinstance(obj, BaseProperty):
             for i in self._props:
                 if obj.name == i.name:
                     return i
@@ -399,6 +403,37 @@ class BaseSection(base.sectionable, Section):
             raise ValueError("odml.Section.contains:"
                              "Section or Property object expected.")
 
+    def merge_check(self, source_section, strict=True):
+        """
+        Recursively checks whether a source Section and all its children can be merged
+        with self and all its children as destination and raises a ValueError if any of
+        the Section attributes definition and reference differ in source and destination.
+
+        :param source_section: an odML Section.
+        :param strict: If True, definition and reference attributes of any merged Sections
+                       as well as most attributes of merged Properties on the same
+                       tree level in source and destination have to be identical.
+        """
+        if strict and self.definition is not None and source_section.definition is not None:
+            self_def = ''.join(map(str.strip, self.definition.split())).lower()
+            other_def = ''.join(map(str.strip, source_section.definition.split())).lower()
+            if self_def != other_def:
+                raise ValueError(
+                    "odml.Section.merge: src and dest definitions do not match!")
+
+        if strict and self.reference is not None and source_section.reference is not None:
+            self_ref = ''.join(map(str.strip, self.reference.lower().split()))
+            other_ref = ''.join(map(str.strip, source_section.reference.lower().split()))
+            if self_ref != other_ref:
+                raise ValueError(
+                    "odml.Section.merge: src and dest references are in conflict!")
+
+        # Check all the way down the rabbit hole / Section tree.
+        for obj in source_section:
+            mine = self.contains(obj)
+            if mine is not None:
+                mine.merge_check(obj, strict)
+
     def merge(self, section=None, strict=True):
         """
         Merges this section with another *section*.
@@ -421,6 +456,16 @@ class BaseSection(base.sectionable, Section):
                 self.include = self._include
             return
 
+        # Check all the way down the tree if the destination source and
+        # its children can be merged with self and its children since
+        # there is no rollback in case of a downstream merge error.
+        self.merge_check(section, strict)
+
+        if self.definition is None and section.definition is not None:
+            self.definition = section.definition
+        if self.reference is None and section.reference is not None:
+            self.reference = section.reference
+
         for obj in section:
             mine = self.contains(obj)
             if mine is not None:

Property handling
Currently it is possible to add anything to the ```properties``` list of a section without any check being performed.
This way 

- non ```odml.Property``` objects can be inserted into the odml structure (eg  ```sec.properties[0] = 'blaa'```)
- inserted ```odml.Property``` objects are not linked properly (eg. ```sec.properties[0] = odml.Property(name='myprop', value=3)``` results in ```sec.properties[0].parent``` is None)

Would it make sense to implement a similar functionality here as used for ```value``` lists in #237? 
diff --git a/test/test_property.py b/test/test_property.py
index f0aa976..9138cae 100644
--- a/test/test_property.py
+++ b/test/test_property.py
@@ -430,9 +430,96 @@ class TestProperty(unittest.TestCase):
         with self.assertRaises(ValueError):
             prop.new_id("crash and burn")
 
+    def test_merge_check(self):
+        # Test dtype check
+        source = Property(name="source", dtype="string")
+        destination = Property(name="destination", dtype="string")
+
+        destination.merge_check(source)
+        source.dtype = "int"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test value check
+        source = Property(name="source", value=[1, 2, 3])
+        destination = Property(name="destination", value=[4, 5, 6])
+        destination.merge_check(source)
+
+        # Test value convertable
+        source = Property(name="source", value=["7", "8"])
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test value not convertable
+        source = Property(name="source", value=["nine", "ten"])
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, False)
+
+        # Test unit check
+        source = Property(name="source", unit="Hz")
+        destination = Property(name="destination", unit="Hz")
+
+        destination.merge_check(source)
+        source.unit = "s"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test uncertainty check
+        source = Property(name="source", uncertainty=0.0)
+        destination = Property(name="destination", uncertainty=0.0)
+
+        destination.merge_check(source)
+        source.uncertainty = 10.0
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test definition check
+        source = Property(name="source", definition="Freude\t schoener\nGoetterfunken\n")
+        destination = Property(name="destination",
+                               definition="FREUDE schoener GOETTERfunken")
+
+        destination.merge_check(source)
+        source.definition = "Freunde schoender Goetterfunken"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test reference check
+        source = Property(name="source", reference="portal.g-node.org")
+        destination = Property(name="destination", reference="portal.g-node.org")
+
+        destination.merge_check(source)
+        source.reference = "portal.g-node.org/odml/terminologies/v1.1"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
+        # Test value origin check
+        source = Property(name="source", value_origin="file")
+        destination = Property(name="destination", value_origin="file")
+
+        destination.merge_check(source)
+        source.value_origin = "other file"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source)
+
+        destination.merge_check(source, False)
+
     def test_merge(self):
         p_dst = Property("p1", value=[1, 2, 3], unit="Hz", definition="Freude\t schoener\nGoetterfunken\n",
-                         reference="portal.g-node.org", uncertainty=0.0)
+                         reference="portal.g-node.org", uncertainty=0.0, value_origin="file")
         p_src = Property("p2", value=[2, 4, 6], unit="Hz", definition="FREUDE schoener GOETTERfunken")
 
         test_p = p_dst.clone()
@@ -451,11 +538,15 @@ class TestProperty(unittest.TestCase):
         p_inv_ref = p_src.clone()
         p_inv_ref.reference = "test"
 
+        p_inv_origin = p_src.clone()
+        p_inv_origin.value_origin = "other file"
+
         test_p = p_dst.clone()
         self.assertRaises(ValueError, test_p.merge, p_inv_unit)
         self.assertRaises(ValueError, test_p.merge, p_inv_def)
         self.assertRaises(ValueError, test_p.merge, p_inv_uncert)
         self.assertRaises(ValueError, test_p.merge, p_inv_ref)
+        self.assertRaises(ValueError, test_p.merge, p_inv_origin)
 
         test_p.reference = None
         test_p.merge(p_src)
@@ -473,6 +564,10 @@ class TestProperty(unittest.TestCase):
         test_p.merge(p_src)
         self.assertEqual(test_p.definition, p_src.definition)
 
+        test_p.value_origin = ""
+        test_p.merge(p_src)
+        self.assertEqual(test_p.value_origin, p_src.value_origin)
+
         double_p = Property("adouble", value=3.14)
         int_p = Property("aint", value=3)
         self.assertRaises(ValueError, double_p.merge, int_p)
diff --git a/test/test_section.py b/test/test_section.py
index d5c625a..84604aa 100644
--- a/test/test_section.py
+++ b/test/test_section.py
@@ -33,6 +33,12 @@ class TestSection(unittest.TestCase):
         sec.reference = "%s_edit" % sec_ref
         self.assertEqual(sec.reference, "%s_edit" % sec_ref)
 
+        # Test setting attributes to None when '' is passed.
+        sec.reference = ""
+        self.assertIsNone(sec.reference)
+        sec.definition = ""
+        self.assertIsNone(sec.definition)
+
     def test_parent(self):
         s = Section("Section")
         self.assertIsNone(s.parent)
@@ -87,6 +93,72 @@ class TestSection(unittest.TestCase):
         subsec.parent = None
         self.assertEqual(subsec.get_path(), "/")
 
+    def test_children(self):
+        sec = Section(name="sec")
+
+        # Test set sections
+        subsec = Section(name="subsec", parent=sec)
+        newsec = Section(name="newsec")
+
+        self.assertEqual(subsec.parent, sec)
+        self.assertEqual(sec.sections[0], subsec)
+        self.assertEqual(len(sec.sections), 1)
+        self.assertIsNone(newsec.parent)
+
+        sec.sections[0] = newsec
+        self.assertEqual(newsec.parent, sec)
+        self.assertEqual(sec.sections[0], newsec)
+        self.assertEqual(len(sec.sections), 1)
+        self.assertIsNone(subsec.parent)
+
+        # Test parent cleanup
+        root = Section(name="root")
+        sec.parent = root
+        subsec.parent = newsec
+
+        self.assertEqual(len(newsec.sections), 1)
+        self.assertEqual(newsec.sections[0], subsec)
+        self.assertEqual(subsec.parent, newsec)
+        self.assertEqual(len(root.sections), 1)
+        self.assertEqual(root.sections[0], sec)
+
+        subsec.parent = root
+        self.assertEqual(len(newsec.sections), 0)
+        self.assertEqual(subsec.parent, root)
+        self.assertEqual(len(root.sections), 2)
+        self.assertEqual(root.sections[1], subsec)
+
+        # Test set section fails
+        with self.assertRaises(ValueError):
+            sec.sections[0] = Document()
+        with self.assertRaises(ValueError):
+            sec.sections[0] = Property("fail")
+        with self.assertRaises(ValueError):
+            sec.sections[0] = "subsec"
+
+        # Test set properties
+        prop = Property(name="prop", parent=sec)
+        newprop = Property(name="newprop")
+
+        self.assertEqual(prop.parent, sec)
+        self.assertEqual(sec.properties[0], prop)
+        self.assertEqual(len(sec.properties), 1)
+        self.assertIsNone(newprop.parent)
+
+        sec.properties[0] = newprop
+        self.assertEqual(newprop.parent, sec)
+        self.assertEqual(sec.properties[0], newprop)
+        self.assertEqual(len(sec.properties), 1)
+        self.assertIsNone(prop.parent)
+
+        # Test set property fails
+        with self.assertRaises(ValueError):
+            sec.properties[0] = Document()
+        with self.assertRaises(ValueError):
+            sec.properties[0] = newsec
+        with self.assertRaises(ValueError):
+            sec.properties[0] = "prop"
+
     def test_id(self):
         s = Section(name="S")
         self.assertIsNotNone(s.id)
@@ -377,6 +449,252 @@ class TestSection(unittest.TestCase):
         with self.assertRaises(ValueError):
             sec.contains("some info")
 
+    def test_merge_check(self):
+        # -- Root level Section checks
+
+        # Test empty Section check
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        destination.merge_check(source, True)
+
+        # Test definition check
+        source = Section(name="source", definition="def")
+        destination = Section(name="destination", definition="def")
+
+        destination.merge_check(source, True)
+        source.definition = "other def"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # Test reference check
+        source = Section(name="source", reference="ref")
+        destination = Section(name="destination", reference="ref")
+
+        destination.merge_check(source, True)
+        source.reference = "other ref"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # -- First child level Section checks
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_sec_one = Section(name="lvl", type="one",
+                            reference="ref", definition="def", parent=source)
+        s_sec_two = Section(name="unrelated", type="one",
+                            reference="one", definition="one", parent=source)
+
+        d_sec_one = Section(name="lvl", type="one",
+                            reference="ref", definition="def", parent=destination)
+        d_sec_two = Section(name="unrelated", type="two",
+                            reference="two", definition="two", parent=destination)
+
+        # Test Section child level definition check
+        destination.merge_check(source, True)
+        s_sec_one.definition = "other def"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # Test Section child level reference check
+        s_sec_one.definition = "def"
+        s_sec_one.reference = "other ref"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # -- Second child level Section checks
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_sec_one = Section(name="lvl", type="one",
+                            reference="ref", definition="def", parent=source)
+        s_subsec_one = Section(name="lvl", type="two",
+                               reference="ref2", definition="def2", parent=s_sec_one)
+        s_sec_two = Section(name="unrelated", type="one",
+                            reference="one", definition="one", parent=source)
+        s_subsec_two = Section(name="lvl", type="two",
+                               reference="none1", definition="none1", parent=s_sec_two)
+
+        d_sec_one = Section(name="lvl", type="one",
+                            reference="ref", definition="def", parent=destination)
+        d_subsec_one = Section(name="lvl", type="two",
+                               reference="ref2", definition="def2", parent=d_sec_one)
+        d_sec_two = Section(name="unrelated", type="two",
+                            reference="two", definition="two", parent=destination)
+        d_subsec_two = Section(name="lvl", type="two",
+                               reference="none2", definition="none2", parent=d_sec_two)
+
+        # Test Section 2nd child level definition check
+        # Check no definition/reference ValueError between s_subsec_two and d_subsec_one
+        # since their parents will not be merged.
+        destination.merge_check(source, True)
+
+        # Raise a definition ValueError between s_subsec_one and d_subsec_one
+        # since their parents will be merged.
+        s_subsec_one.definition = "other def"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # Test Section 2nd child level reference check
+        s_subsec_one.definition = "def2"
+
+        # Raise a reference ValueError between s_subsec_one and d_subsec_one
+        # since their parents will be merged.
+        s_subsec_one.reference = "other ref"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # -- Root level Property checks
+        # All Property checks will only test unit failure in the Section merge context.
+        # Other failures are covered by the specific Property merge check tests.
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_prop = Property(name="prop", parent=source)
+        d_prop = Property(name="prop", parent=destination)
+
+        destination.merge_check(source, True)
+        s_prop.unit = "Hz"
+        d_prop.unit = "s"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # -- First child level Property checks
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_prop_one = Property(name="lvl one", unit="Hz", parent=source)
+        s_prop_two = Property(name="unrelated one", unit="one", parent=source)
+
+        d_prop_one = Property(name="lvl one", unit="Hz", parent=destination)
+        d_prop_two = Property(name="unrelated two", unit="two", parent=destination)
+
+        # Test Property child level check
+        destination.merge_check(source, True)
+
+        # Test raise ValueError between s_prop_one and d_prop_one
+        s_prop_one.unit = "other unit"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+        # -- Second child level Property checks
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_sec_one = Section(name="lvl", type="one", parent=source)
+        s_subprop_one = Property(name="lvl one", unit="Hz", parent=s_sec_one)
+
+        s_sec_two = Section(name="unrelated", type="one", parent=source)
+        s_subprop_two = Property(name="unrelated one", unit="one", parent=s_sec_two)
+
+        d_sec_one = Section(name="lvl", type="one", parent=destination)
+        d_subprop_one = Property(name="lvl one", unit="Hz", parent=d_sec_one)
+
+        d_sec_two = Section(name="unrelated", type="two", parent=destination)
+        d_subprop_two = Property(name="unrelated one", unit="two", parent=d_sec_two)
+
+        # Test Property 2nd child level definition check
+        # Check no unit ValueError between s_subprop_two and d_subprop_one
+        # since their parents will not be merged.
+        destination.merge_check(source, True)
+
+        # Raise a unit ValueError between s_subprop_one and d_subprop_one
+        # since their parents will be merged.
+        s_subprop_one.unit = "other unit"
+        with self.assertRaises(ValueError):
+            destination.merge_check(source, True)
+
+    def test_merge(self):
+        # -- Root level Section merge tests
+        source = Section(name="source", definition="def", reference="ref")
+        destination = Section(name="destination")
+
+        destination.merge(source)
+        self.assertEqual(destination.definition, source.definition)
+        self.assertEqual(destination.reference, source.reference)
+
+        # -- First child level Section merge tests
+        s_sec_one = Section(name="lvl", type="one", definition="def", parent=source)
+        s_sec_two = Section(name="other", type="one", parent=source)
+        d_sec_one = Section(name="lvl", type="one", parent=destination)
+
+        self.assertEqual(len(destination), 1)
+        self.assertIsNone(destination.sections["lvl"].definition)
+        self.assertIsNone(destination.sections["lvl"].reference)
+
+        destination.merge(source)
+        self.assertEqual(len(destination), 2)
+        self.assertEqual(destination.sections["lvl"].definition, s_sec_one.definition)
+        self.assertEqual(destination.sections["lvl"].reference, s_sec_one.reference)
+        self.assertEqual(destination.sections["other"], s_sec_two)
+
+        # -- Root level Property merge tests
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_prop_one = Property(name="prop_one", unit="Hz", parent=source)
+        s_prop_two = Property(name="prop_two", parent=source)
+        d_prop_one = Property(name="prop_one", parent=destination)
+
+        self.assertEqual(len(destination.properties), 1)
+        self.assertIsNone(destination.properties["prop_one"].unit)
+
+        destination.merge(source)
+        self.assertEqual(len(destination.properties), 2)
+        self.assertEqual(destination.properties["prop_one"].unit, s_prop_one.unit)
+        self.assertEqual(destination.properties["prop_two"], s_prop_two)
+
+        # -- First child level Property merge tests
+        source = Section(name="source")
+        destination = Section(name="destination")
+
+        s_sec_one = Section(name="lvl", type="one", definition="def", parent=source)
+        s_prop_one = Property(name="prop_one", unit="Hz", parent=s_sec_one)
+        s_prop_two = Property(name="prop_two", parent=s_sec_one)
+
+        d_sec_one = Section(name="lvl", type="one", parent=destination)
+        d_prop_one = Property(name="prop_one", parent=d_sec_one)
+
+        self.assertEqual(len(destination.properties), 0)
+        self.assertEqual(len(destination.sections["lvl"].properties), 1)
+        self.assertIsNone(destination.sections["lvl"].properties["prop_one"].unit)
+
+        destination.merge(source)
+        self.assertEqual(len(destination.properties), 0)
+        self.assertEqual(len(destination.sections["lvl"].properties), 2)
+        self.assertEqual(destination.sections["lvl"].properties["prop_one"].unit,
+                         s_prop_one.unit)
+        self.assertEqual(destination.sections["lvl"].properties["prop_two"],
+                         s_prop_two)
+
+        # -- Test nothing merged on second child level ValueError
+        source = Section(name="source", definition="def", reference="ref")
+        destination = Section(name="destination")
+
+        s_sec_one = Section(name="lvl", type="one", definition="def", parent=source)
+        s_sec_two = Section(name="other", type="one", parent=source)
+        d_sec_one = Section(name="lvl", type="one", parent=destination)
+
+        s_subprop_one = Property(name="prop", value=[1, 2, 3], parent=s_sec_one)
+        d_subprop_one = Property(name="prop", value=["four", "five"], parent=d_sec_one)
+
+        self.assertEqual(len(destination.sections), 1)
+        self.assertEqual(len(destination.sections["lvl"].properties), 1)
+        self.assertIsNone(destination.definition)
+        self.assertIsNone(destination.sections["lvl"].definition)
+        self.assertEqual(destination.sections["lvl"].properties[0].value,
+                         d_subprop_one.value)
+
+        with self.assertRaises(ValueError):
+            destination.merge(source)
+
+        self.assertEqual(len(destination.sections), 1)
+        self.assertEqual(len(destination.sections["lvl"].properties), 1)
+        self.assertIsNone(destination.definition)
+        self.assertIsNone(destination.sections["lvl"].definition)
+        self.assertEqual(destination.sections["lvl"].properties[0].value,
+                         d_subprop_one.value)
+
     def test_link(self):
         pass
 
@@ -386,8 +704,5 @@ class TestSection(unittest.TestCase):
     def test_repository(self):
         pass
 
-    def test_merge(self):
-        pass
-
     def test_unmerge(self):
         pass

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
isodate==0.7.2
lxml==5.3.1
-e git+https://github.com/G-Node/python-odml.git@a66ecdf4c0d2ad3fbd098b72ad3d8705a43ccd3d#egg=odML
packaging==24.2
pluggy==1.5.0
pyparsing==3.2.3
pytest==8.3.5
PyYAML==6.0.2
rdflib==7.1.4
tomli==2.2.1

name: python-odml
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - isodate==0.7.2
      - lxml==5.3.1
      - packaging==24.2
      - pluggy==1.5.0
      - pyparsing==3.2.3
      - pytest==8.3.5
      - pyyaml==6.0.2
      - rdflib==7.1.4
      - tomli==2.2.1
prefix: /opt/conda/envs/python-odml

test/test_property.py::TestProperty::test_merge_check test/test_section.py::TestSection::test_children test/test_section.py::TestSection::test_merge test/test_section.py::TestSection::test_merge_check test/test_section.py::TestSection::test_simple_attributes
test/test_property.py::TestProperty::test_bool_conversion test/test_property.py::TestProperty::test_clone test/test_property.py::TestProperty::test_dtype test/test_property.py::TestProperty::test_get_merged_equivalent test/test_property.py::TestProperty::test_get_path test/test_property.py::TestProperty::test_get_set_value test/test_property.py::TestProperty::test_id test/test_property.py::TestProperty::test_merge test/test_property.py::TestProperty::test_new_id test/test_property.py::TestProperty::test_parent test/test_property.py::TestProperty::test_simple_attributes test/test_property.py::TestProperty::test_str_to_int_convert test/test_property.py::TestProperty::test_value test/test_property.py::TestProperty::test_value_append test/test_property.py::TestProperty::test_value_extend test/test_section.py::TestSection::test_append test/test_section.py::TestSection::test_clone test/test_section.py::TestSection::test_contains test/test_section.py::TestSection::test_extend test/test_section.py::TestSection::test_id test/test_section.py::TestSection::test_include test/test_section.py::TestSection::test_insert test/test_section.py::TestSection::test_link test/test_section.py::TestSection::test_new_id test/test_section.py::TestSection::test_parent test/test_section.py::TestSection::test_path test/test_section.py::TestSection::test_remove test/test_section.py::TestSection::test_reorder test/test_section.py::TestSection::test_repository test/test_section.py::TestSection::test_unmerge
odml/base.py odml/section.py odml/property.py
odml/base.py odml/section.py odml/property.py