Skip to content

Array-Elemente zu einem String mit Trennzeichen verbinden snippet

Eine Liste zu einem einzigen String mit Trennzeichen verflachen — CSV-Zeilen, IN-Klauseln, Log-Felder.

Eine Liste zu einem einzigen String mit Trennzeichen verflachen — CSV-Zeilen, IN-Klauseln, Log-Felder. Das Trennzeichen darf nur ZWISCHEN den Elementen stehen, nie nach dem letzten, deshalb ist die handgeschriebene Schleife mit hängendem Komma der klassische Bug. Die andere Falle ist, was join mit Schrotteinträgen macht: JavaScript und Ruby wandeln null/undefined zu '' um, während Python, Java und Kotlin fröhlich 'None'/'null' ausgeben; JavaScript flattet verschachtelte Arrays eine Ebene, und PHP warnt dabei. Fast jede höhere Sprache hat ein Ein-Aufruf-join; C nicht (größenbewusste Schleife unten), und in SQL ist das Verbinden eines Arrays (array_to_string) eine andere Operation als das Aggregieren von Zeilen (string_agg).

Runnable recipe · 15 languages
Text & Parsingstringsarraysjoinformatting

Every language

15 languages, copy-ready. One at a time with syntax highlighting, or all inline.

SQLSQLrunnable
-- PostgreSQL: join an ARRAY with a delimiter
SELECT array_to_string(ARRAY['go', 'rust', 'zig'], ', ') AS langs;
-- langs: go, rust, zig

-- Joining ROWS is a different job — aggregate, don't array-join:
SELECT string_agg(name, ', ' ORDER BY name) AS langs
FROM (VALUES ('zig'), ('go'), ('rust')) AS t(name);
-- langs: go, rust, zig

array_to_string joins an array; string_agg (STRING_AGG in SQL Server, GROUP_CONCAT in MySQL) concatenates rows — mixing them up is the perennial SQL mistake. NULLs vanish in both by default; MySQL's GROUP_CONCAT needs SEPARATOR ', ' and truncates at group_concat_max_len (default 1024).

Run in the SQL playground →
JSJavaScript
const langs = ['go', 'rust', 'zig'];
console.log(langs.join(', ')); // go, rust, zig

// join() COERCES instead of throwing — know the rules:
console.log([1, null, undefined, [2, 3]].join('|')); // 1|||2,3
// null/undefined become '' and nested arrays flatten ONE level,
// so the separator inside the nested array is ',' — not yours.

[] .join() returns '' (never throws), and a Symbol element is the only thing that raises TypeError. If nulls should disappear rather than render as empty fields, filter them before joining.

TSTypeScript
const parts: string[] = ['a', 'b', 'c'];
const csv: string = parts.join(',');

// Typed arrays of string | null join fine at runtime but lie about
// content — scrub before joining to keep the type honest:
const dirty: (string | null)[] = ['a', null, 'c'];
const clean: string = dirty.filter((p): p is string => p !== null).join(',');
console.log(csv, clean); // a,b,c a,c

join() accepts readonly arrays too (readonly string[]), so functions taking lists they will not mutate can join without copying.

GoGo
package main

import (
	"fmt"
	"strings"
)

func main() {
	langs := []string{"go", "rust", "zig"}
	fmt.Println(strings.Join(langs, ", ")) // go, rust, zig
}

strings.Join takes []string only — convert other element types first with a map. It never appends the trailing separator and []string{} yields "". For numbers, fmt.Sprint(a, b, ...) inserts spaces between operands, so it is not a join.

RsRust
fn main() {
    let langs = vec!["go", "rust", "zig"];
    println!("{}", langs.join(", ")); // go, rust, zig

    // Non-strings: map to String first, then join.
    let nums: Vec<String> = [1, 2, 3].iter().map(|n| n.to_string()).collect();
    println!("{}", nums.join("-")); // 1-2-3
}

join comes from the Join slice trait — it works on Vec<&str>, Vec<String> and iterators collected into either, and borrows nothing you do not hand it. The separator is a str, and an empty slice yields an empty String.

PHPPHP
<?php
$langs = ['go', 'rust', 'zig'];
echo implode(', ', $langs), PHP_EOL; // go, rust, zig

// null silently becomes ''; scalars are cast; nested arrays WARN:
$dirty = ['a', null, 3];
echo implode('|', $dirty), PHP_EOL; // a||3

implode (alias join) is argument-order agnostic since PHP 8 — implode($sep, $arr) and implode($arr, $sep) both work. A nested array raises 'Array to string conversion', so map first with array_map('strval', $arr) for mixed data.

PyPython
langs = ["go", "rust", "zig"]
print(", ".join(langs))                          # go, rust, zig
print("-".join(str(n) for n in [1, None, 3]))   # 1-None-3 — None is NOT skipped

# Non-str elements raise TypeError, and the message is the teacher:
try:
    ", ".join(["a", 1])
except TypeError as err:
    print(err)  # sequence item 1: expected str instance, int found

join is a STRING method, not a list method — '" ".join(items)', never 'items.join(...)'. Contrast with JavaScript: Python stringifies None as 'None' rather than dropping it to ''.

CC
#include <stdio.h>
#include <string.h>

/* Join n parts with sep into out (size bytes). NUL-terminates, truncates
   safely on overflow, and never emits the separator after the last part.
   Returns out. */
char *join(char *out, size_t size, const char *sep, const char **parts, size_t n) {
    size_t pos = 0;
    out[0] = '\0';
    for (size_t i = 0; i < n; i++) {
        size_t len = strlen(parts[i]);
        size_t sep_len = i > 0 ? strlen(sep) : 0;
        if (pos + sep_len + len + 1 > size) {
            break; /* not enough room for part + NUL — stop here */
        }
        if (i > 0) {
            memcpy(out + pos, sep, sep_len);
            pos += sep_len;
        }
        memcpy(out + pos, parts[i], len);
        pos += len;
        out[pos] = '\0';
    }
    return out;
}

int main(void) {
    const char *parts[] = {"go", "rust", "zig"};
    char buf[64];
    printf("%s\n", join(buf, sizeof buf, ", ", parts, 3)); /* go, rust, zig */
    return 0;
}

No stdlib join exists — the bugs are always the same two: a separator after the last element and a buffer overflow. The explicit size argument and the room-for-NUL check are what make this safe; snprintf into a cursor is the other accepted idiom.

C++C++
#include <iostream>
#include <string>
#include <vector>

// Separator BETWEEN elements only — the `if (i > 0)` guard is what
// kills the trailing-separator bug. C++23 adds std::views::join_with.
std::string join(const std::vector<std::string>& parts, const std::string& sep) {
    std::string out;
    for (std::size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) out += sep;
        out += parts[i];
    }
    return out;
}

int main() {
    std::cout << join({"go", "rust", "zig"}, ", ") << '\n'; // go, rust, zig
}

reserve() the output when sizes are known, or fold with std::accumulate for one-liners. fmt::join (or std::format's ranged join in C++23) formats non-strings without pre-converting.

C#C#
string[] langs = { "go", "rust", "zig" };
Console.WriteLine(string.Join(", ", langs)); // go, rust, zig

// Accepts IEnumerable<T> and calls ToString; null renders as empty:
Console.WriteLine(string.Join(", ", new object?[] { 1, null, "x" })); // 1, , x

string.Join overloads take params arrays, IEnumerable<string>, and IEnumerable<T> — so LINQ sequences join without materializing. For comma-critical output (CSV), quote fields separately; Join does no escaping.

JvJava
import java.util.List;

public class JoinDemo {
    public static void main(String[] args) {
        List<String> langs = List.of("go", "rust", "zig");
        System.out.println(String.join(", ", langs)); // go, rust, zig
    }
}

String.join accepts CharSequence only (String, StringBuilder, ...) — map other types first. Null elements are NOT dropped: String.join(",", "a", null) prints 'a,null'. For streams use Collectors.joining(", ").

SwSwift
let langs = ["go", "rust", "zig"]
print(langs.joined(separator: ", ")) // go, rust, zig

// Non-strings: map to String first.
print([1, 2, 3].map(String.init).joined(separator: "-")) // 1-2-3

joined() on [String] yields String, but joined(separator:) on a slice of Substring views is also fine. The no-separator joined() concatenates directly. Empty arrays yield "" — never an optional.

KtKotlin
fun main() {
    val langs = listOf("go", "rust", "zig")
    println(langs.joinToString(", ")) // go, rust, zig

    println(listOf(1, null, 3).joinToString("-")) // 1-null-3
}

joinToString carries the kitchen sink: prefix = "[", postfix = "]", limit = 3, truncated = "…" — logging-friendly out of the box. Like Java, null elements render as the literal 'null'.

RbRuby
langs = %w[go rust zig]
puts langs.join(', ') # go, rust, zig

# nil stringifies to '' — and nested arrays are flattened RECURSIVELY,
# with your separator applied inside them too:
puts [1, nil, [2, 3]].join('|').inspect # "1||2|3"

Array#join flattens nested arrays to arbitrary depth, which quietly changes your delimiter structure. %w[...] literals are the idiomatic source of string arrays; [].join(',') is "".

ZigZig
const std = @import("std");

pub fn main() !void {
    const parts = [_][]const u8{ "go", "rust", "zig" };
    const sep = ", ";

    // Size the buffer exactly (saturating sub guards the empty case),
    // allocate once, then fill — no trailing separator.
    var total: usize = 0;
    for (parts) |p| total += p.len;
    total += sep.len * (parts.len -| 1);

    const a = std.heap.page_allocator;
    const out = try a.alloc(u8, total);
    defer a.free(out);

    var pos: usize = 0;
    for (parts, 0..) |p, i| {
        if (i > 0) {
            @memcpy(out[pos..][0..sep.len], sep);
            pos += sep.len;
        }
        @memcpy(out[pos..][0..p.len], p);
        pos += p.len;
    }
    std.debug.print("{s}\n", .{out}); // go, rust, zig
}

No stdlib join — allocate total + separators in one shot, then memcpy each part. `parts.len -| 1` (saturating) keeps the empty-array case from underflowing, and Zig 0.11+ multi-object for loops give you the index for the separator test.