diff --git a/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/BiopetExecutable.scala b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/BiopetExecutable.scala
index 0acc5f348c65a878926d64e3482008ff037505f8..943039fd7ece4f8c22ca07ce46f8d614c5631e80 100644
--- a/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/BiopetExecutable.scala
+++ b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/BiopetExecutable.scala
@@ -34,7 +34,8 @@ object BiopetExecutable extends Logging {
       nl.lumc.sasc.biopet.tools.BedtoolsCoverageToCounts,
       nl.lumc.sasc.biopet.tools.SageCountFastq,
       nl.lumc.sasc.biopet.tools.SageCreateLibrary,
-      nl.lumc.sasc.biopet.tools.SageCreateTagCounts)
+      nl.lumc.sasc.biopet.tools.SageCreateTagCounts,
+      nl.lumc.sasc.biopet.tools.SamplesTsvToJson)
   )
 
   /**
diff --git a/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/config/Config.scala b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/config/Config.scala
index c35a1fdd71501b3ecddd721185ee99a271de9434..219374597ca3a780c35e7c8585e2a97ca53d6a1d 100644
--- a/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/config/Config.scala
+++ b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/core/config/Config.scala
@@ -197,4 +197,28 @@ object Config {
       return None
     }
   }
+
+  def mapToJson(map: Map[String, Any]): Json = {
+    map.foldLeft(jEmptyObject)((acc, kv) => (kv._1 := {
+      kv._2 match {
+        case m: Map[_, _] => mapToJson(m.map(m => m._1.toString -> anyToJson(m._2)))
+        case _            => anyToJson(kv._2)
+      }
+    }) ->: acc)
+  }
+
+  def anyToJson(any: Any): Json = {
+    any match {
+      case j: Json      => j
+      case m: Map[_, _] => mapToJson(m.map(m => m._1.toString -> anyToJson(m._2)))
+      case l: List[_]   => Json.array(l.map(anyToJson(_)): _*)
+      case n: Int       => Json.jNumberOrString(n)
+      case n: Double    => Json.jNumberOrString(n)
+      case n: Long      => Json.jNumberOrString(n)
+      case n: Short     => Json.jNumberOrString(n)
+      case n: Float     => Json.jNumberOrString(n)
+      case n: Byte      => Json.jNumberOrString(n)
+      case _            => jString(any.toString)
+    }
+  }
 }
\ No newline at end of file
diff --git a/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/tools/SamplesTsvToJson.scala b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/tools/SamplesTsvToJson.scala
new file mode 100644
index 0000000000000000000000000000000000000000..da69c171a3e788b747c002917936fa2053a58c62
--- /dev/null
+++ b/biopet-framework/src/main/scala/nl/lumc/sasc/biopet/tools/SamplesTsvToJson.scala
@@ -0,0 +1,46 @@
+package nl.lumc.sasc.biopet.tools
+
+import java.io.File
+import nl.lumc.sasc.biopet.core.ToolCommand
+import scala.io.Source
+import nl.lumc.sasc.biopet.core.config.Config
+
+object SamplesTsvToJson extends ToolCommand {
+  case class Args(inputFiles: List[File] = Nil) extends AbstractArgs
+
+  class OptParser extends AbstractOptParser {
+    opt[File]('i', "inputFiles") required () unbounded () valueName ("<file>") action { (x, c) =>
+      c.copy(inputFiles = x :: c.inputFiles)
+    } text ("Input must be a tsv file, first line is seen as header and must at least have a 'sample' column, 'library' column is optional, multiple files allowed")
+  }
+  def main(args: Array[String]): Unit = {
+    val argsParser = new OptParser
+    val commandArgs: Args = argsParser.parse(args, Args()) getOrElse sys.exit(1)
+
+    val fileMaps = for (inputFile <- commandArgs.inputFiles) yield {
+      val reader = Source.fromFile(inputFile)
+      val lines = reader.getLines.toList
+      val header = lines.head.split("\t")
+      val sampleColumn = header.indexOf("sample")
+      val libraryColumn = header.indexOf("library")
+      if (sampleColumn == -1) throw new IllegalStateException("sample column does not exist in: " + inputFile)
+
+      val librariesValues: List[Map[String, Any]] = for (tsvLine <- lines.tail) yield {
+        val values = tsvLine.split("\t")
+        val sample = values(sampleColumn)
+        val library = if (libraryColumn != -1) values(libraryColumn) else null
+        val valuesMap = (for (t <- 0 until values.size if t != sampleColumn if t != libraryColumn) yield (header(t) -> values(t))).toMap
+        val map: Map[String, Any] = if (library != null) {
+          Map("samples" -> Map(sample -> Map("libraries" -> Map(library -> valuesMap))))
+        } else {
+          Map("samples" -> Map(sample -> valuesMap))
+        }
+        map
+      }
+      librariesValues.foldLeft(Map[String, Any]())((acc, kv) => Config.mergeMaps(acc, kv))
+    }
+    val map = fileMaps.foldLeft(Map[String, Any]())((acc, kv) => Config.mergeMaps(acc, kv))
+    val json = Config.mapToJson(map)
+    println(json.spaces2)
+  }
+}