let rec to_json: type a. a t -> json =
  fun w ->
    let call name (args : json list): json = `List (`String name :: args) in
    match w with
    | Fastq_gz file -> call "Fastq_gz" [`String file#product#path]
    | Fastq file -> call "Fastq" [`String file#product#path]
    | Bam_sample (name, file) ->
      call "Bam-sample" [`String name; `String file#product#path]
    | Bam_to_fastq (name, how, bam) ->
      let how_string =
        match how with `Paired -> "Paired" | `Single -> "Single" in
      call "Bam-to-fastq" [`String how_string; to_json bam]
    | Paired_end_sample ({sample_name; fragment_id}, r1, r2) ->
      call "Paired-end" [`String sample_name; `String fragment_id;
                         to_json r1; to_json r2]
    | Single_end_sample ({sample_name; fragment_id}, r) ->
      call "Single-end" [`String sample_name; `String fragment_id; to_json r]
    | Gunzip_concat fastq_gz_list ->
      call "Gunzip-concat" (List.map ~f:to_json fastq_gz_list)
    | Concat_text fastq_list ->
      call "Concat" (List.map ~f:to_json fastq_list)
    | Bwa (config, input) ->
      call "BWA" [
        `Assoc ["configuration"Bwa.Configuration.Aln.to_json config];
        to_json input
      ]
    | Bwa_mem (params, input) ->
      let input_json = to_json input in
      call "BWA-MEM" [
        `Assoc ["configuration"Bwa.Configuration.Mem.to_json params];
        input_json
      ]
    | Star (conf, input) ->
      let input_json = to_json input in
      call "STAR" [
        `Assoc ["configuration"Star.Configuration.Align.to_json conf];
        input_json;
      ]
    | Hisat (conf, input) ->
      let input_json = to_json input in
      call "HISAT" [
        `Assoc ["configuration"Hisat.Configuration.to_json conf];
        input_json;
      ]
    | Stringtie (conf, input) ->
      let input_json = to_json input in
      call "Stringtie" [
        `Assoc ["configuration"Stringtie.Configuration.to_json conf];
        input_json;
      ]
    | Mosaik (input) ->
      let input_json = to_json input in
      call "MOSAIK" [input_json]
    | Gatk_indel_realigner ((indel_cfg, target_cfg), bam) ->
      let open Gatk.Configuration in
      let input_json = to_json bam in
      let indel_cfg_json = Indel_realigner.to_json indel_cfg in
      let target_cfg_json = Realigner_target_creator.to_json target_cfg in
      call "Gatk_indel_realigner" [`Assoc [
          "Configuration"`Assoc [
            "IndelRealigner Configuration", indel_cfg_json;
            "RealignerTargetCreator Configuration", target_cfg_json;
          ];
          "Input", input_json;
        ]]
    | Gatk_bqsr ((bqsr_cfg, print_reads_cfg), bam) ->
      let open Gatk.Configuration in
      let input_json = to_json bam in
      call "Gatk_bqsr" [`Assoc [
          "Configuration"`Assoc [
            "Bqsr"Bqsr.to_json bqsr_cfg;
            "Print_reads"Print_reads.to_json print_reads_cfg;
          ];
          "Input", input_json;
        ]]
    | Germline_variant_caller (gvc, bam) ->
      call gvc.Variant_caller.name [`Assoc [
          "Configuration", gvc.Variant_caller.configuration_json;
          "Input", to_json bam;
        ]]
    | Picard_mark_duplicates (settings, bam) ->
      (* The settings should not impact the output, so we don't dump them. *)
      call "Picard_mark_duplicates" [`Assoc ["input", to_json bam]]
    | Bam_pair (normal, tumor) ->
      call "Bam-pair" [`Assoc ["normal", to_json normal; "tumor", to_json tumor]]
    | Somatic_variant_caller (svc, bam_pair) ->
      call svc.Variant_caller.name [`Assoc [
          "Configuration", svc.Variant_caller.configuration_json;
          "Input", to_json bam_pair;
        ]]
    | Seq2HLA input ->
      call "Seq2HLA" [`Assoc [
          "Input", to_json input
        ]]
    | Optitype (kind, input) ->
      call "Optitype" [`Assoc [
          "Input", to_json input;
          "Kind"`String (match kind with `DNA -> "DNA" | `RNA -> "RNA")
        ]]
    | With_metadata (_, p) -> to_json p