diff --git a/nomad/metainfo/__init__.py b/nomad/metainfo/__init__.py
index 34206203fdd15c80f172b9f9b4af1fa1ab1fca08..fbde5985a3ad7e9b974bf2c89ced0eb2e5bb61bf 100644
--- a/nomad/metainfo/__init__.py
+++ b/nomad/metainfo/__init__.py
@@ -1 +1 @@
-from .metainfo import MSection, Section, Quantity, Enum, units
+from .metainfo import MSection, MCategory, Definition, Section, Quantity, Category, Package, Enum, units
diff --git a/nomad/metainfo/example.py b/nomad/metainfo/example.py
new file mode 100644
index 0000000000000000000000000000000000000000..b13cee7f08a08bfb336a939e6c4c73ef1b5f2a8b
--- /dev/null
+++ b/nomad/metainfo/example.py
@@ -0,0 +1,96 @@
+""" An example metainfo package. """
+
+import numpy as np
+
+from nomad.metainfo import MSection, MCategory, Section, Quantity, Enum, Package, units
+
+m_package = Package(links=['http://metainfo.nomad-coe.eu'])
+
+
+class SystemHash(MCategory):
+    """ All quantities that contribute to what makes a system unique. """
+
+
+class Run(MSection):
+    """ All data that belongs to a single code run. """
+
+    code_name = Quantity(type=str, description='The name of the code that was run.')
+    code_version = Quantity(type=str, description='The version of the code that was run.')
+
+
+class VaspRun(MSection):
+    """ All VASP specific quantities for section Run. """
+    m_def = Section(extends=Run.m_def)
+
+    x_vasp_raw_format = Quantity(
+        type=Enum(['xml', 'outcar']),
+        description='The file format of the parsed VASP mainfile.')
+
+
+class Parsing(MSection):
+    """ All data that describes the NOMAD parsing of this run. """
+    m_def = Section(parent=Run.m_def)
+
+    parser_name = Quantity(type=str)
+    parser_version = Quantity(type=str)
+    nomad_version = Quantity(type=str)
+    warnings = Quantity(type=str, shape=['0..*'])
+
+
+class System(MSection):
+    """ All data that describes a simulated system. """
+    m_def = Section(repeats=True, parent=Run.m_def)
+
+    n_atoms = Quantity(
+        type=int, default=0,
+        description='Number of atoms in the simulated system.')
+
+    atom_labels = Quantity(
+        type=str, shape=['n_atoms'], categories=[SystemHash.m_def],
+        description='The atoms in the simulated systems.')
+
+    atom_positions = Quantity(
+        type=np.dtype('f'), shape=['n_atoms', 3], unit=units.m, categories=[SystemHash.m_def],
+        description='The atom positions in the simulated system.')
+
+    lattice_vectors = Quantity(
+        type=np.dtype('f'), shape=[3, 3], unit=units.m, categories=[SystemHash.m_def],
+        description='The lattice vectors of the simulated unit cell.')
+
+    periodic_dimensions = Quantity(
+        type=bool, shape=[3], categories=[SystemHash.m_def],
+        description='A vector of booleans indicating in which dimensions the unit cell is repeated.')
+
+
+if __name__ == '__main__':
+    # Demonstration of how to reflect on the definitions
+
+    # All definitions are metainfo data themselves, and they can be accessed like any other
+    # metainfo data. E.g. all section definitions are sections themselves.
+
+    # To get quantities of a given section
+    print(Run.m_def.m_sub_sections(Quantity))
+
+    # Or all Sections in the package
+    print(m_package.m_sub_sections(Section))  # type: ignore, pylint: disable=undefined-variable
+
+    # There are also some definition specific helper methods.
+    # For example to get all attributes (Quantities and possible sub-sections) of a section.
+    print(Run.m_def.attributes)
+
+    # Demonstration on how to use the definitions, e.g. to create a run with system:
+    run = Run()
+    run.code_name = 'VASP'
+    run.code_version = '1.0.0'
+
+    system = run.m_create(System)
+    system.n_atoms = 3
+    system.atom_labels = ['H', 'H', 'O']
+
+    # Or to read data from existing metainfo data:
+    print(system.atom_labels)
+
+    # To serialize the data:
+    print(run.m_to_json(indent=2))
+
+    # print(m_package.m_to_json(indent=2))  # type: ignore, pylint: disable=undefined-variable
diff --git a/nomad/metainfo/metainfo.py b/nomad/metainfo/metainfo.py
index bc919e86b1396fc8984019fbb91ecf250d5a5780..7d8c3aa3b7db1c56856975e4c3c8e08df30afe88 100644
--- a/nomad/metainfo/metainfo.py
+++ b/nomad/metainfo/metainfo.py
@@ -137,13 +137,12 @@ See the reference of classes :class:`Section` and :class:`Quantities` for detail
 """
 
 # TODO validation
-# TODO serialization/deserialization
-# TODO packages
 
 from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict, cast
 import sys
 import inspect
 import re
+import json
 
 import numpy as np
 from pint.unit import _Unit
@@ -301,7 +300,7 @@ class MSection(metaclass=MObjectMeta):
         # transfer name and description to m_def
         m_def.name = cls.__name__
         if cls.__doc__ is not None:
-            m_def.description = inspect.cleandoc(cls.__doc__)
+            m_def.description = inspect.cleandoc(cls.__doc__).strip()
         m_def.section_cls = cls
 
         # add sub_section to parent section
@@ -314,7 +313,7 @@ class MSection(metaclass=MObjectMeta):
             if isinstance(attr, Quantity):
                 attr.name = name
                 if attr.description is not None:
-                    attr.description = inspect.cleandoc(attr.description)
+                    attr.description = inspect.cleandoc(attr.description).strip()
                     attr.__doc__ = attr.description
                 # manual manipulation of m_data due to bootstrapping
                 m_def.m_data.setdefault('Quantity', []).append(attr)
@@ -476,7 +475,8 @@ class MSection(metaclass=MObjectMeta):
 
         return sub_section
 
-    def m_create(self, definition: SectionDef, **kwargs) -> 'MSection':
+    # TODO this should work with the section constructor
+    def m_create(self, definition: Type[MSectionBound], **kwargs) -> MSectionBound:
         """Creates a subsection and adds it this this section
 
         Args:
@@ -495,7 +495,7 @@ class MSection(metaclass=MObjectMeta):
         section_cls = section_def.section_cls
         section_instance = section_cls(m_def=section_def, m_parent=self, **kwargs)
 
-        return self.m_add_sub_section(section_instance)
+        return cast(MSectionBound, self.m_add_sub_section(section_instance))
 
     def __resolve_quantity(self, definition: Union[str, 'Quantity']) -> 'Quantity':
         """Resolves and checks the given quantity definition. """
@@ -568,11 +568,25 @@ class MSection(metaclass=MObjectMeta):
                 else:
                     yield name, self.m_data[name].m_to_dict()
 
-            for name in self.m_def.quantities:
+            for name, quantity in self.m_def.quantities.items():
                 if name in self.m_data:
                     value = getattr(self, name)
                     if hasattr(value, 'tolist'):
                         value = value.tolist()
+
+                    # TODO
+                    if isinstance(quantity.type, Section):
+                        value = str(value)
+                    # TODO
+                    if isinstance(value, type):
+                        value = str(value)
+                    # TODO
+                    if isinstance(value, np.dtype):
+                        value = str(value)
+                    # TODO
+                    if isinstance(value, _Unit):
+                        value = str(value)
+
                     yield name, value
 
         return {key: value for key, value in items()}
@@ -604,9 +618,9 @@ class MSection(metaclass=MObjectMeta):
         section_instance.m_update(**dct)
         return section_instance
 
-    def m_to_json(self):
+    def m_to_json(self, **kwargs):
         """Returns the data of this section as a json string. """
-        pass
+        return json.dumps(self.m_to_dict(), **kwargs)
 
     def m_all_contents(self) -> Iterable[Content]:
         """Returns an iterable over all sub and sub subs sections. """
@@ -651,7 +665,7 @@ class MCategory(metaclass=MObjectMeta):
         # transfer name and description to m_def
         m_def.name = cls.__name__
         if cls.__doc__ is not None:
-            m_def.description = inspect.cleandoc(cls.__doc__)
+            m_def.description = inspect.cleandoc(cls.__doc__).strip()
 
         # add section cls' section to the module's package
         module_name = cls.__module__
@@ -894,7 +908,7 @@ class Package(Definition):
 
         pkg.name = module_name
         if pkg.description is None and module.__doc__ is not None:
-            pkg.description = inspect.cleandoc(module.__doc__)
+            pkg.description = inspect.cleandoc(module.__doc__).strip()
 
         return pkg
 
diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py
index 4b7f8cbae673915da74ed54d649a439cf42aa62d..98a4f938f951d80d93ac78f847730e7867a47419 100644
--- a/tests/test_metainfo.py
+++ b/tests/test_metainfo.py
@@ -15,7 +15,8 @@
 import pytest
 import numpy as np
 
-from nomad.metainfo.metainfo import MSection, MCategory, Section, Quantity, Definition, Category, Package, sub_section
+from nomad.metainfo.metainfo import MSection, MCategory, Section, Quantity, Definition, Category, sub_section
+from nomad.metainfo.example import Run, System, SystemHash, Parsing, m_package as example_package
 
 
 def assert_section_def(section_def: Section):
@@ -79,37 +80,11 @@ class TestPureReflection:
         assert getattr(obj, 'test_quantity') == 'test_value'
 
 
-m_package = Package(description='package doc')
-
-
 class MaterialDefining(MCategory):
     """Quantities that add to what constitutes a different material."""
     pass
 
 
-class Run(MSection):
-    """ This is the description.
-
-    And some more description.
-    """
-
-    code_name = Quantity(
-        type=str, description='''
-        The code_name description.
-        ''')
-
-
-class System(MSection):
-    m_def = Section(repeats=True, parent=Run.m_def)
-    n_atoms = Quantity(type=int, default=0, categories=[MaterialDefining.m_def])
-    atom_label = Quantity(type=str, shape=['n_atoms'], categories=[MaterialDefining.m_def])
-    atom_positions = Quantity(type=np.dtype('f8'), shape=['n_atoms', 3])
-
-
-class Parsing(MSection):
-    m_def = Section(parent=Run.m_def)
-
-
 class TestM2:
     """ Test for meta-info definitions. """
 
@@ -125,7 +100,7 @@ class TestM2:
         assert Run.m_def.parent is None
 
     def test_quantities(self):
-        assert len(Run.m_def.quantities) == 1
+        assert len(Run.m_def.quantities) == 2
         assert Run.m_def.quantities['code_name'] == Run.__dict__['code_name']
 
     def test_sub_sections(self):
@@ -133,7 +108,7 @@ class TestM2:
         assert Run.m_def.sub_sections['System'] == System.m_def
 
     def test_attributes(self):
-        assert len(Run.m_def.attributes) == 3
+        assert len(Run.m_def.attributes) == 4
         assert Run.m_def.attributes['System'] == System.m_def
         assert Run.m_def.attributes['code_name'] == Run.__dict__['code_name']
 
@@ -162,19 +137,19 @@ class TestM2:
 
     def test_quantity_description(self):
         assert Run.code_name.description is not None
-        assert Run.code_name.description == 'The code_name description.'
+        assert Run.code_name.description == 'The name of the code that was run.'
         assert Run.code_name.description.strip() == Run.code_name.description.strip()
 
     def test_direct_category(self):
-        assert len(System.atom_label.categories)
-        assert MaterialDefining.m_def in System.atom_label.categories
-        assert System.atom_label in MaterialDefining.m_def.definitions
+        assert len(System.atom_labels.categories) == 1
+        assert SystemHash.m_def in System.atom_labels.categories
+        assert System.atom_labels in SystemHash.m_def.definitions
 
     def test_package(self):
-        assert m_package.name == __name__
-        assert m_package.description is not None
-        assert len(m_package.m_sub_sections(Section)) == 3
-        assert len(m_package.m_sub_sections(Category)) == 1
+        assert example_package.name == 'nomad.metainfo.example'
+        assert example_package.description == 'An example metainfo package.'
+        assert len(example_package.m_sub_sections(Section)) == 4
+        assert len(example_package.m_sub_sections(Category)) == 1
 
 
 class TestM1:
@@ -206,7 +181,7 @@ class TestM1:
 
     def test_defaults(self):
         assert System().n_atoms == 0
-        assert System().atom_label is None
+        assert System().atom_labels is None
         try:
             System().does_not_exist
             assert False, 'Supposed unreachable'
@@ -268,7 +243,7 @@ class TestM1:
 
     def test_wrong_shape_2(self):
         try:
-            System().atom_label = 'label'
+            System().atom_labels = 'label'
             assert False, 'Supposed unreachable'
         except TypeError:
             pass
@@ -286,7 +261,7 @@ class TestM1:
         run.code_name = 'test code name'
         system: System = run.m_create(System)
         system.n_atoms = 3
-        system.atom_label = ['H', 'H', 'O']
+        system.atom_labels = ['H', 'H', 'O']
         system.atom_positions = np.array([[1.2e-10, 0, 0], [0, 1.2e-10, 0], [0, 0, 1.2e-10]])
 
         return run
@@ -299,7 +274,7 @@ class TestM1:
         assert_section_instance(system)
         assert system.m_def == System.m_def
         assert system.n_atoms == 3
-        assert system.atom_label == ['H', 'H', 'O']
+        assert system.atom_labels == ['H', 'H', 'O']
         assert type(system.atom_positions) == np.ndarray
 
     def test_to_dict(self, example_data):