diff --git a/PKG-INFO b/PKG-INFO
index 10d2af839859e665c70f477bb55d0f69069a8680..bc16f4c8c7ebb08c67566494e31ed46b024a646f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,14 +1,14 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.0.48
+Version: 0.0.49
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
 Author-email: swh-devel@inria.fr
 License: UNKNOWN
-Project-URL: Funding, https://www.softwareheritage.org/donate
 Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Project-URL: Source, https://forge.softwareheritage.org/source/swh-model
+Project-URL: Funding, https://www.softwareheritage.org/donate
 Description: swh-model
         =========
         
@@ -35,5 +35,5 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
 Classifier: Operating System :: OS Independent
 Classifier: Development Status :: 5 - Production/Stable
 Description-Content-Type: text/markdown
-Provides-Extra: testing
 Provides-Extra: cli
+Provides-Extra: testing
diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO
index 10d2af839859e665c70f477bb55d0f69069a8680..bc16f4c8c7ebb08c67566494e31ed46b024a646f 100644
--- a/swh.model.egg-info/PKG-INFO
+++ b/swh.model.egg-info/PKG-INFO
@@ -1,14 +1,14 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.0.48
+Version: 0.0.49
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
 Author-email: swh-devel@inria.fr
 License: UNKNOWN
-Project-URL: Funding, https://www.softwareheritage.org/donate
 Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Project-URL: Source, https://forge.softwareheritage.org/source/swh-model
+Project-URL: Funding, https://www.softwareheritage.org/donate
 Description: swh-model
         =========
         
@@ -35,5 +35,5 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
 Classifier: Operating System :: OS Independent
 Classifier: Development Status :: 5 - Production/Stable
 Description-Content-Type: text/markdown
-Provides-Extra: testing
 Provides-Extra: cli
+Provides-Extra: testing
diff --git a/swh/model/cli.py b/swh/model/cli.py
index 991bc46ec72bf9657d7db9965b33e08ab83ff29d..ec33310b05722dc1fe1325e3bcb489737a42547a 100644
--- a/swh/model/cli.py
+++ b/swh/model/cli.py
@@ -69,6 +69,13 @@ def pid_of_git_repo(path):
             }
         else:
             branches[ref] = None
+
+    for ref, target in repo.refs.get_symrefs().items():
+        branches[ref] = {
+            'target': target,
+            'target_type': 'alias',
+        }
+
     snapshot = {'branches': branches}
 
     pid = pids.PersistentId(object_type='snapshot',
diff --git a/swh/model/model.py b/swh/model/model.py
index 6824217d4977eef188e0a81cd6e8ed6610ce6498..217e3632b6d2976a55b90f54f581ccf68bcb7776 100644
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -28,7 +28,21 @@ class BaseModel:
     def to_dict(self):
         """Wrapper of `attr.asdict` that can be overridden by subclasses
         that have special handling of some of the fields."""
-        return attr.asdict(self)
+
+        def dictify(value):
+            if isinstance(value, BaseModel):
+                return value.to_dict()
+            elif isinstance(value, Enum):
+                return value.value
+            elif isinstance(value, dict):
+                return {k: dictify(v) for k, v in value.items()}
+            elif isinstance(value, list):
+                return [dictify(v) for v in value]
+            else:
+                return value
+
+        ret = attr.asdict(self, recurse=False)
+        return dictify(ret)
 
     @classmethod
     def from_dict(cls, d):
@@ -127,7 +141,6 @@ class OriginVisit(BaseModel):
         ov = super().to_dict()
         if ov['visit'] is None:
             del ov['visit']
-        ov['origin'] = self.origin.to_dict()
         return ov
 
     @classmethod
@@ -178,11 +191,6 @@ class SnapshotBranch(BaseModel):
                 raise ValueError('Wrong length for bytes identifier: %d' %
                                  len(value))
 
-    def to_dict(self):
-        branch = attr.asdict(self)
-        branch['target_type'] = branch['target_type'].value
-        return branch
-
     @classmethod
     def from_dict(cls, d):
         return cls(
@@ -196,15 +204,6 @@ class Snapshot(BaseModel):
     id = attr.ib(type=Sha1Git)
     branches = attr.ib(type=Dict[bytes, Optional[SnapshotBranch]])
 
-    def to_dict(self):
-        return {
-            'id': self.id,
-            'branches': {
-                name: branch.to_dict() if branch else None
-                for (name, branch) in self.branches.items()
-            }
-        }
-
     @classmethod
     def from_dict(cls, d):
         return cls(
@@ -237,9 +236,7 @@ class Release(BaseModel):
             raise ValueError('release date must be None if author is None.')
 
     def to_dict(self):
-        rel = attr.asdict(self)
-        rel['date'] = self.date.to_dict() if self.date is not None else None
-        rel['target_type'] = rel['target_type'].value
+        rel = super().to_dict()
         if rel['metadata'] is None:
             del rel['metadata']
         return rel
@@ -280,13 +277,6 @@ class Revision(BaseModel):
     parents = attr.ib(type=List[Sha1Git],
                       default=attr.Factory(list))
 
-    def to_dict(self):
-        rev = attr.asdict(self)
-        rev['date'] = self.date.to_dict()
-        rev['committer_date'] = self.committer_date.to_dict()
-        rev['type'] = rev['type'].value
-        return rev
-
     @classmethod
     def from_dict(cls, d):
         d = d.copy()
@@ -316,11 +306,6 @@ class Directory(BaseModel):
     id = attr.ib(type=Sha1Git)
     entries = attr.ib(type=List[DirectoryEntry])
 
-    def to_dict(self):
-        dir_ = attr.asdict(self)
-        dir_['entries'] = [entry.to_dict() for entry in self.entries]
-        return dir_
-
     @classmethod
     def from_dict(cls, d):
         return cls(
@@ -367,7 +352,7 @@ class Content(BaseModel):
                 'Must not provide a reason if content is not absent.')
 
     def to_dict(self):
-        content = attr.asdict(self)
+        content = super().to_dict()
         for field in ('data', 'reason', 'ctime'):
             if content[field] is None:
                 del content[field]
diff --git a/swh/model/tests/test_cli.py b/swh/model/tests/test_cli.py
index 990ca08ea3cf1a6c7d68b802f05c6af50495bce3..f20da7a83a6d9a77d185033fe77c52b6b5d8ed82 100644
--- a/swh/model/tests/test_cli.py
+++ b/swh/model/tests/test_cli.py
@@ -23,7 +23,7 @@ class TestIdentify(DataMixin, unittest.TestCase):
         super().setUp()
         self.runner = CliRunner()
 
-    def assertPidOK(self, result, pid):  # noqa: N802
+    def assertPidOK(self, result, pid):
         self.assertEqual(result.exit_code, 0)
         self.assertEqual(result.output.split()[0], pid)
 
@@ -56,8 +56,9 @@ class TestIdentify(DataMixin, unittest.TestCase):
                 repo_dir = os.path.join(d, 'sample-repo')
                 result = self.runner.invoke(cli.identify,
                                             ['--type', 'snapshot', repo_dir])
-                self.assertPidOK(result,
-                                 'swh:1:snp:9dc0fc035aabe293f5faf6c362a59513454a170d')  # NoQA
+                self.assertPidOK(
+                    result,
+                    'swh:1:snp:abc888898124270905a0ef3c67e872ce08e7e0c1')
 
     def test_origin_id(self):
         """identify an origin URL"""
diff --git a/version.txt b/version.txt
index ac1c1caa880802e7a35a421dec20f66df38107bd..82adc2c9a903d7d7f42b49a983552ae720e5a8d2 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-v0.0.48-0-gb2c21d3
\ No newline at end of file
+v0.0.49-0-g4b79a2b
\ No newline at end of file