Repository: matchms/matchms (python)

Issue:
[feature request] add functionality to export metadata in tabular format
**Is your feature request related to a problem? Please describe.**
To my knowledge, there is no functionality to export metadata of one or multiple spectra to `csv`, `tsv`, or other tabular formats. Such a feature could be useful in molecular networking workflows. For example, [MetGem](https://metgem.github.io/) does not read metadata from `.msp` files but expects users to supply a [metadata table](https://metgem.readthedocs.io/en/latest/user_manual/import.html#metadata).

**Describe the solution you'd like**
Expand `exporting` module with a function to export metadata. The function would take a list of spectra, a path, and an output format as inputs.
Optionally, the user could pass the keys to be exported. Not sure if that's a good idea or not.

**Any good starting points?**
An example of writing metadata to csv:
```python
import csv

spectra: List[matchms.Spectrum]
keys: Set[str]  # union of all metadata keys to be exported

with open("metadata.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, keys)
    writer.writeheader()
    writer.writerows([spectrum.metadata for spectrum in spectra])
```

Alternatively, this can be done by creating a `pandas.DataFrame` and exporting with `pandas.DataFrame.to_csv` but this will introduce a new dependency to `matchms`.

**Scientific reference**
Possible use case: molecular networking with MetGem https://doi.org/10.1021/acs.analchem.8b03099


Pull request:
Create metadata_export.py
Closes #372 

Patch:
diff --git a/matchms/exporting/metadata_export.py b/matchms/exporting/metadata_export.py
new file mode 100644
index 00000000..c6330ab2
--- /dev/null
+++ b/matchms/exporting/metadata_export.py
@@ -0,0 +1,109 @@
+import csv
+import json
+from typing import Any, Dict, List, Optional, Set, Tuple
+import numpy as np
+from ..Spectrum import Spectrum
+
+
+def _get_metadata_dict(spectrum: Spectrum, include_fields: Optional[List[str]] = None) -> Dict[str, Any]:
+    """Extract keys from spectrum metadata. Will silently continue if a key is not found.
+
+    Args:
+        spectrum (Spectrum): Spectrum with metadata to extract.
+        include_fields (List[str] | str): "all" or set of metadata keys to extract.
+
+    Returns:
+        dict[str, Any]: Dictionary containing the specified keys.
+    """
+    if include_fields is None or include_fields[0] == "all":
+        return spectrum.metadata
+    if not isinstance(include_fields, list):
+        print("'Include_fields' must be 'all' or list of keys.")
+        return None
+        
+    return {key: spectrum.metadata[key] for key in spectrum.metadata.keys()
+            & include_fields}
+
+
+def export_metadata_as_json(spectrums: List[Spectrum], filename: str,
+                            include_fields: Optional[List[str]] = None):
+    """Export metadata to json file.
+
+    Parameters
+    ----------
+    spectrums:
+        Expected input is a list of  :py:class:`~matchms.Spectrum.Spectrum` objects.
+    filename:
+        Provide filename to save metadata of spectrum(s) as json file.
+    identifier:
+        Identifier used for naming each spectrum in the output file.
+    """
+    metadata_dicts = []
+    for spec in spectrums:
+        metadata_dict = _get_metadata_dict(spec, include_fields)
+        if metadata_dict:
+            metadata_dicts.append(metadata_dict)
+
+    with open(filename, 'w', encoding="utf-8") as fout:
+        json.dump(metadata_dicts, fout)
+
+
+def export_metadata_as_csv(spectra: List[Spectrum], filename: str,
+                           include_fields: Optional[List[str]] = None):
+    """Export metadata to csv file.
+
+    Parameters
+    ----------
+    spectra:
+        Expected input is a list of  :py:class:`~matchms.Spectrum.Spectrum` objects.
+    filename:
+        Provide filename to save metadata of spectrum(s) as csv file.
+    identifier:
+        Identifier used for naming each spectrum in the output file.
+    """
+    metadata, columns = get_metadata_as_array(spectra)
+
+    if include_fields is not None:
+        metadata, columns = _subset_metadata(include_fields, metadata, columns)
+
+    with open(filename, 'a', encoding="utf-8") as csvfile:  #TODO: assert if file exists
+        writer = csv.writer(csvfile)
+        writer.writerow(columns)
+        for data in metadata:
+            writer.writerow(data)
+
+def _subset_metadata(include_fields: List[str], metadata: np.array, columns: Set[str]) -> Tuple[np.array, Set[str]]:
+    """Subset metadata to 'include_fields' and return intersection of columns.
+
+    Args:
+        include_fields (List[str]): Columns to include.
+        metadata (np.array): Data to subset.
+        columns (set[str]): Set of columns present in data
+
+    Returns:
+        Tuple[np.array, set[str]]: Subset data and columns.
+    """
+    return metadata[include_fields], columns.intersection(include_fields)
+
+
+def get_metadata_as_array(spectra: List[Spectrum]) -> Tuple[np.array, List[str]]:
+    """Extract union of all metadata as numpy array from all spectra.
+
+    Args:
+        spectra (List[Spectrum]): Spectra from which to collect metadata.
+
+    Returns:
+        Tuple[np.array, List[str]]: Metadata and union of all columns detected in all spectra.
+    """
+    keys = spectra[0].metadata.keys()
+    for s in spectra:
+        keys |= s.metadata.keys()
+
+    values = []
+
+    for s in spectra:
+        v = tuple((s.get(k) for k in keys))
+        values.append(v)
+    
+    d = np.array(values, dtype=[(k, np.chararray) for k in keys])
+    return d, keys
\ No newline at end of file


Test patch:
diff --git a/tests/expected_metadata.csv b/tests/expected_metadata.csv
new file mode 100644
index 00000000..0ba0559c
--- /dev/null
+++ b/tests/expected_metadata.csv
@@ -0,0 +1,11 @@
+name,synonym,db#,inchikey,mw,formula,precursormz,accession,author,license,exact mass,instrument,instrument type,ms level,ionization energy,ion type,ionization mode,last auto-curation,smiles,inchi,molecular formula,total exact mass,smiles_2,num peaks
+1-NITROPYRENE,1-NITROPYRENE,JP000001,ALRLPDGCPYIVHP-UHFFFAOYSA-N,247.063328528,C16H9NO2,0,JP000001,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,247.06333,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335755,[O-1][N+1](=O)c(c4)c(c1)c(c3c4)c(c2cc3)c(ccc2)c1,InChI=1S/C16H9NO2/c18-17(19)14-9-7-12-5-4-10-2-1-3-11-6-8-13(14)16(12)15(10)11/h1-9H,C16H9NO2,247.063328528,[H]C=1C([H])=C2C([H])=C([H])C3=C([H])C([H])=C(C=4C([H])=C([H])C(C1[H])=C2C34)N(=O)=O,75
+"2,4-DINITROPHENOL","2,4-DINITROPHENOL",JP000002,UFBJCMHMOXMLKC-UHFFFAOYSA-N,184.01202122799998,C6H4N2O5,0,JP000002,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,184.01202,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335764,[O-1][N+1](=O)c(c1)cc([N+1]([O-1])=O)c(O)c1,"InChI=1S/C6H4N2O5/c9-6-2-1-4(7(10)11)3-5(6)8(12)13/h1-3,9H",C6H4N2O5,184.01202122799998,[H]OC1=C([H])C([H])=C(C([H])=C1N(=O)=O)N(=O)=O,64
+"3,4-DICHLOROPHENOL","3,4-DICHLOROPHENOL",JP000003,WDNBURPWRNALGP-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000003,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335820,Oc(c1)cc(Cl)c(Cl)c1,"InChI=1S/C6H4Cl2O/c7-5-2-1-4(9)3-6(5)8/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC1=C([H])C([H])=C(Cl)C(Cl)=C1[H],36
+"2,5-DICHLOROPHENOL","2,5-DICHLOROPHENOL",JP000004,RANCECPPZPIPNO-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000004,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335825,Oc(c1)c(Cl)ccc(Cl)1,"InChI=1S/C6H4Cl2O/c7-4-1-2-5(8)6(9)3-4/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC1=C([H])C(Cl)=C([H])C([H])=C1Cl,44
+"2,6-DICHLOROPHENOL","2,6-DICHLOROPHENOL",JP000005,HOLHYSJJBXSLMV-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000005,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335848,Clc(c1)c(O)c(Cl)cc1,"InChI=1S/C6H4Cl2O/c7-4-2-1-3-5(8)6(4)9/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC=1C(Cl)=C([H])C([H])=C([H])C1Cl,33
+"2,3-DICHLOROPHENOL","2,3-DICHLOROPHENOL",JP000006,UMPSXRYVXUPCOS-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000006,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335870,Oc(c1)c(Cl)c(Cl)cc1,"InChI=1S/C6H4Cl2O/c7-4-2-1-3-5(9)6(4)8/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC=1C([H])=C([H])C([H])=C(Cl)C1Cl,42
+"2,4-DICHLOROPHENOL","2,4-DICHLOROPHENOL",JP000007,HFZWRUODUSTPEG-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000007,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210335864,Oc(c1)c(Cl)cc(Cl)c1,"InChI=1S/C6H4Cl2O/c7-4-1-2-6(9)5(8)3-4/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC1=C([H])C([H])=C(Cl)C([H])=C1Cl,37
+"3,5-DICHLOROPHENOL","3,5-DICHLOROPHENOL",JP000008,VPOMSPZBQMDLTM-UHFFFAOYSA-N,161.963920108,C6H4Cl2O,0,JP000008,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,161.96392,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210336053,Oc(c1)cc(Cl)cc(Cl)1,"InChI=1S/C6H4Cl2O/c7-4-1-5(8)3-6(9)2-4/h1-3,9H",C6H4Cl2O,161.963920108,[H]OC=1C([H])=C(Cl)C([H])=C(Cl)C1[H],32
+"2,4,5-TRICHLOROPHENOL","2,4,5-TRICHLOROPHENOL",JP000009,LHJGJYXLEPZJPM-UHFFFAOYSA-N,195.924947756,C6H3Cl3O,0,JP000009,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,195.92495,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210336033,Oc(c1)c(Cl)cc(Cl)c(Cl)1,"InChI=1S/C6H3Cl3O/c7-3-1-5(9)6(10)2-4(3)8/h1-2,10H",C6H3Cl3O,195.924947756,[H]OC1=C([H])C(Cl)=C(Cl)C([H])=C1Cl,65
+"2,4,6-TRICHLOROPHENOL","2,4,6-TRICHLOROPHENOL",JP000010,LINPIYWFGCPVIE-UHFFFAOYSA-N,195.924947756,C6H3Cl3O,0,JP000010,"KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",CC BY-NC-SA,195.92495,VARIAN MAT-44,EI-B,MS1,70 eV,[M]+*,positive,1495210336053,Clc(c1)cc(Cl)c(O)c(Cl)1,"InChI=1S/C6H3Cl3O/c7-3-1-4(8)6(10)5(9)2-3/h1-2,10H",C6H3Cl3O,195.924947756,[H]OC=1C(Cl)=C([H])C(Cl)=C([H])C1Cl,66
diff --git a/tests/expected_metadata.json b/tests/expected_metadata.json
new file mode 100644
index 00000000..c8c83e84
--- /dev/null
+++ b/tests/expected_metadata.json
@@ -0,0 +1,262 @@
+[
+    {
+        "synonym": "1-NITROPYRENE",
+        "db#": "JP000001",
+        "inchikey": "ALRLPDGCPYIVHP-UHFFFAOYSA-N",
+        "mw": "247.063328528",
+        "formula": "C16H9NO2",
+        "accession": "JP000001",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "[O-1][N+1](=O)c(c4)c(c1)c(c3c4)c(c2cc3)c(ccc2)c1",
+        "inchi": "InChI=1S/C16H9NO2/c18-17(19)14-9-7-12-5-4-10-2-1-3-11-6-8-13(14)16(12)15(10)11/h1-9H",
+        "smiles_2": "[H]C=1C([H])=C2C([H])=C([H])C3=C([H])C([H])=C(C=4C([H])=C([H])C(C1[H])=C2C34)N(=O)=O",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335755",
+        "molecular_formula": "C16H9NO2",
+        "total_exact_mass": "247.063328528",
+        "num_peaks": "75",
+        "compound_name": "1-NITROPYRENE",
+        "precursor_mz": 0.0,
+        "parent_mass": "247.06333"
+    },
+    {
+        "synonym": "2,4-DINITROPHENOL",
+        "db#": "JP000002",
+        "inchikey": "UFBJCMHMOXMLKC-UHFFFAOYSA-N",
+        "mw": "184.01202122799998",
+        "formula": "C6H4N2O5",
+        "accession": "JP000002",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "[O-1][N+1](=O)c(c1)cc([N+1]([O-1])=O)c(O)c1",
+        "inchi": "InChI=1S/C6H4N2O5/c9-6-2-1-4(7(10)11)3-5(6)8(12)13/h1-3,9H",
+        "smiles_2": "[H]OC1=C([H])C([H])=C(C([H])=C1N(=O)=O)N(=O)=O",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335764",
+        "molecular_formula": "C6H4N2O5",
+        "total_exact_mass": "184.01202122799998",
+        "num_peaks": "64",
+        "compound_name": "2,4-DINITROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "184.01202"
+    },
+    {
+        "synonym": "3,4-DICHLOROPHENOL",
+        "db#": "JP000003",
+        "inchikey": "WDNBURPWRNALGP-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000003",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)cc(Cl)c(Cl)c1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-5-2-1-4(9)3-6(5)8/h1-3,9H",
+        "smiles_2": "[H]OC1=C([H])C([H])=C(Cl)C(Cl)=C1[H]",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335820",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "36",
+        "compound_name": "3,4-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "2,5-DICHLOROPHENOL",
+        "db#": "JP000004",
+        "inchikey": "RANCECPPZPIPNO-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000004",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)c(Cl)ccc(Cl)1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-4-1-2-5(8)6(9)3-4/h1-3,9H",
+        "smiles_2": "[H]OC1=C([H])C(Cl)=C([H])C([H])=C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335825",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "44",
+        "compound_name": "2,5-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "2,6-DICHLOROPHENOL",
+        "db#": "JP000005",
+        "inchikey": "HOLHYSJJBXSLMV-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000005",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Clc(c1)c(O)c(Cl)cc1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-4-2-1-3-5(8)6(4)9/h1-3,9H",
+        "smiles_2": "[H]OC=1C(Cl)=C([H])C([H])=C([H])C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335848",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "33",
+        "compound_name": "2,6-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "2,3-DICHLOROPHENOL",
+        "db#": "JP000006",
+        "inchikey": "UMPSXRYVXUPCOS-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000006",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)c(Cl)c(Cl)cc1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-4-2-1-3-5(9)6(4)8/h1-3,9H",
+        "smiles_2": "[H]OC=1C([H])=C([H])C([H])=C(Cl)C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335870",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "42",
+        "compound_name": "2,3-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "2,4-DICHLOROPHENOL",
+        "db#": "JP000007",
+        "inchikey": "HFZWRUODUSTPEG-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000007",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)c(Cl)cc(Cl)c1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-4-1-2-6(9)5(8)3-4/h1-3,9H",
+        "smiles_2": "[H]OC1=C([H])C([H])=C(Cl)C([H])=C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210335864",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "37",
+        "compound_name": "2,4-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "3,5-DICHLOROPHENOL",
+        "db#": "JP000008",
+        "inchikey": "VPOMSPZBQMDLTM-UHFFFAOYSA-N",
+        "mw": "161.963920108",
+        "formula": "C6H4Cl2O",
+        "accession": "JP000008",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)cc(Cl)cc(Cl)1",
+        "inchi": "InChI=1S/C6H4Cl2O/c7-4-1-5(8)3-6(9)2-4/h1-3,9H",
+        "smiles_2": "[H]OC=1C([H])=C(Cl)C([H])=C(Cl)C1[H]",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210336053",
+        "molecular_formula": "C6H4Cl2O",
+        "total_exact_mass": "161.963920108",
+        "num_peaks": "32",
+        "compound_name": "3,5-DICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "161.96392"
+    },
+    {
+        "synonym": "2,4,5-TRICHLOROPHENOL",
+        "db#": "JP000009",
+        "inchikey": "LHJGJYXLEPZJPM-UHFFFAOYSA-N",
+        "mw": "195.924947756",
+        "formula": "C6H3Cl3O",
+        "accession": "JP000009",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Oc(c1)c(Cl)cc(Cl)c(Cl)1",
+        "inchi": "InChI=1S/C6H3Cl3O/c7-3-1-5(9)6(10)2-4(3)8/h1-2,10H",
+        "smiles_2": "[H]OC1=C([H])C(Cl)=C(Cl)C([H])=C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210336033",
+        "molecular_formula": "C6H3Cl3O",
+        "total_exact_mass": "195.924947756",
+        "num_peaks": "65",
+        "compound_name": "2,4,5-TRICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "195.92495"
+    },
+    {
+        "synonym": "2,4,6-TRICHLOROPHENOL",
+        "db#": "JP000010",
+        "inchikey": "LINPIYWFGCPVIE-UHFFFAOYSA-N",
+        "mw": "195.924947756",
+        "formula": "C6H3Cl3O",
+        "accession": "JP000010",
+        "author": "KOGA M, UNIV. OF OCCUPATIONAL AND ENVIRONMENTAL HEALTH",
+        "license": "CC BY-NC-SA",
+        "instrument": "VARIAN MAT-44",
+        "smiles": "Clc(c1)cc(Cl)c(O)c(Cl)1",
+        "inchi": "InChI=1S/C6H3Cl3O/c7-3-1-4(8)6(10)5(9)2-3/h1-2,10H",
+        "smiles_2": "[H]OC=1C(Cl)=C([H])C(Cl)=C([H])C1Cl",
+        "instrument_type": "EI-B",
+        "ms_level": "MS1",
+        "ionization_energy": "70 eV",
+        "ion_type": "[M]+*",
+        "ionization_mode": "positive",
+        "last_auto-curation": "1495210336053",
+        "molecular_formula": "C6H3Cl3O",
+        "total_exact_mass": "195.924947756",
+        "num_peaks": "66",
+        "compound_name": "2,4,6-TRICHLOROPHENOL",
+        "precursor_mz": 0.0,
+        "parent_mass": "195.92495"
+    }
+]
\ No newline at end of file
diff --git a/tests/test_exporting_metadata.py b/tests/test_exporting_metadata.py
new file mode 100644
index 00000000..7a2e2329
--- /dev/null
+++ b/tests/test_exporting_metadata.py
@@ -0,0 +1,59 @@
+import filecmp
+import os
+import numpy as np
+import pytest
+from matchms.exporting.metadata_export import (_get_metadata_dict,
+                                               _subset_metadata,
+                                               export_metadata_as_csv,
+                                               export_metadata_as_json,
+                                               get_metadata_as_array)
+from matchms.importing import load_from_msp
+from tests.builder_Spectrum import SpectrumBuilder
+
+
+@pytest.fixture
+def spectra():
+    module_root = os.path.dirname(__file__)
+    inpath = os.path.join(module_root, "MoNA-export-GC-MS-first10.msp")
+    spectra = list(load_from_msp(inpath))
+    return spectra
+
+
+def test_get_metadata_dict():
+    spectrum = SpectrumBuilder().with_metadata({"test": "peter", "inchi": "16583"}).build()
+    assert _get_metadata_dict(spectrum, ["all"]) == {"test": "peter", "inchi": "16583"}
+    assert _get_metadata_dict(spectrum) == {"test": "peter", "inchi": "16583"}
+    assert _get_metadata_dict(spectrum, ["inchi"]) == {"inchi": "16583"}
+    assert _get_metadata_dict(spectrum, ["smiles"]) == {}
+
+
+def test_get_metadata_as_array(spectra):
+    actual, colnames = get_metadata_as_array(spectra)
+    assert len(actual) == 10
+    assert len(colnames) == 24
+    
+
+def test_export_as_csv(tmp_path, spectra):
+    expected = os.path.join(os.path.dirname(__file__), "expected_metadata.csv")    
+    outpath = tmp_path / "metadata.csv"
+    export_metadata_as_csv(spectra, outpath)
+
+    filecmp.cmp(outpath, expected)
+
+
+def test_subset_metadata(spectra):
+    actual, colnames = get_metadata_as_array(spectra)
+
+    subset = ["inchi", "smiles"]
+    newdata, newcolnames = _subset_metadata(subset, actual, colnames)
+
+    np.testing.assert_equal(newdata, actual[subset])
+    assert newcolnames == set(subset)
+
+
+def test_export_metadata_as_json(tmp_path, spectra):
+    outpath = tmp_path / "metadata.json"
+    expected = os.path.join(os.path.dirname(__file__), "expected_metadata.json")    
+
+    export_metadata_as_json(spectra, outpath)
+    filecmp.cmp(outpath, expected)