CSV to Code Converter

A CSV to code converter turns a spreadsheet into a literal data structure you can paste into a source file. Sixteen languages, from a pandas DataFrame to a Kotlin data class list. Every column gets its real type once, every string is escaped by that language's own rules, and any column with a blank cell is declared nullable rather than left to crash on its first run.

Want to filter down to a fixture set first? Open the app

Three things a generic printer gets wrong

It is easy to write something that turns rows into brackets and commas. It is the details underneath that decide whether the snippet you paste actually works.

Quoting is per language. A single quote inside a Ruby single-quoted string, a $ inside a PHP double-quoted string, a backtick inside a JavaScript template literal, and an apostrophe inside a SQL literal all need different treatment. The SQL one is the clearest example: the standard says you double the quote, so O'Brien becomes 'O''Brien'. Backslash-escaping it works in MySQL's default mode and fails in Postgres. None of these produce a syntax error you would notice; they produce a program that runs and holds the wrong string.

Identifiers are per language. order id is not a legal field name in Go, Java, C#, Swift or Kotlin, and each has a different house convention for what it should become. Go and C# want OrderId, Swift and Kotlin want orderId. R will silently rewrite it to order.id unless you back-quote it and pass check.names = FALSE.

Null is per language. An empty cell is not an empty string. It is None, nil, null, NA or NULL depending on where it is going, and writing "" into a numeric column is how a program crashes on its first piece of arithmetic.

The same four rows, in four languages

Starting from this, with a padded SKU and one blank discount:

sku,name,price,in_stock,discount
00412,Bracket 40mm,4.25,true,10
00907,Bracket 90mm,6.80,true,10
01730,Bronze bushing,2.15,true,
04510,Spur gear,31.50,false,5

pandas gets column arrays and an explicit dtype:

import pandas as pd

data = pd.DataFrame({
    "sku": ["00412", "00907", "01730", "04510"],
    "name": ["Bracket 40mm", "Bracket 90mm", "Bronze bushing", "Spur gear"],
    "price": ["4.25", "6.80", "2.15", "31.50"],
    "in_stock": [True, True, True, False],
    "discount": [10, 10, None, 5],
})

TypeScript gets an interface with the nullable column marked:

export interface DataRow {
  sku: string;
  name: string;
  price: string;
  in_stock: boolean;
  discount: number | null;
}

export const data: DataRow[] = [
  { sku: "00412", name: "Bracket 40mm", price: "4.25", in_stock: true, discount: 10 },
  ...
];

Go gets a struct, PascalCase fields, struct tags carrying the original headings, and a pointer for the nullable column:

type Data struct {
    Sku      string   `json:"sku"`
    Name     string   `json:"name"`
    Price    string   `json:"price"`
    InStock  bool     `json:"in_stock"`
    Discount *float64 `json:"discount"`
}

SQL gets a VALUES list with quoted identifiers and a real NULL:

INSERT INTO data ("sku", "name", "price", "in_stock", "discount") VALUES
    ('00412', 'Bracket 40mm', '4.25', TRUE, 10),
    ('01730', 'Bronze bushing', '2.15', TRUE, NULL);

Notice that price is a string in all four. 6.80 as a float prints back as 6.8, so the whole column stays text and the cent survives. If that is not what you want, round the column in the app first and the type detection will follow.

Nullability is not a detail

In the statically typed dialects, a column holding at least one blank cell is declared nullable: number | null in TypeScript, *float64 in Go, and the trailing question mark in C#, Swift and Kotlin. This is reported in the warnings with a count.

It is the difference between a snippet that compiles and one that compiles and then throws. A Swift struct with a non-optional Double cannot hold the missing discount at all, so the code would not compile; a Kotlin one with a non-nullable Double is the same story. Getting the nullability from the data rather than assuming it means the type you paste in is the type your data actually has.

Go needs a small extra: it has no address-of operator for a literal, so &10.0 is not valid and a pointer field cannot be filled inline. When any column is nullable, three one-line helpers are emitted alongside the struct to take the address of a value. That is the idiomatic workaround and it saves you writing it.

Java is the one dialect where the warning is a caution rather than a fix. Map.ofEntries throws a NullPointerException on a null value at runtime, and there is no null-tolerant immutable-map factory in the standard library. The nulls are written and you are told to swap to a HashMap or filter the blanks before running it, which is more useful than quietly dropping the entries.

Small things each dialect needs

pandas gets an astype call for its numeric columns. Any column containing a None is inferred as the object dtype, which is slow, compares strangely, and behaves differently under groupby. Setting the nullable Float64 dtype gives you a column that keeps both its type and its missing values.

polars gets an explicit schema for the same reason, which is also faster than letting it infer.

R gets stringsAsFactors = FALSE and check.names = FALSE. The first stops older R turning every character column into a factor; the second stops R rewriting your headings behind your back.

Ruby uses symbol keys where the heading allows it, and quoted string keys where it does not, which is what a hand-written fixture looks like. The array is frozen.

Java uses Map.ofEntries rather than Map.of, because Map.of takes at most ten key-value pairs and any table with six columns would overflow it.

JavaScript and TypeScript use bare keys where the heading is a legal identifier and quoted keys where it is not, so the output reads like something a person typed rather than something a machine printed.

When not to use this

Embedded literals are for fixtures, test data, small lookup tables and examples in documentation. Somewhere between a few hundred and a few thousand rows, they stop being a good idea: compilation slows down, editors give up on syntax highlighting, and a one-cell change produces a diff nobody can review.

Past that point, keep the CSV as a file and read it at runtime. The row cap here exists to make that boundary visible: pick the first 10, 50 or 100 rows for a fixture and you are told how many of how many you took.

The other case where you want something else is a database load. A VALUES list of 50,000 rows is a slow insert compared with COPY or LOAD DATA INFILE. The CSV to SQL page handles batching and a CREATE TABLE for that job.

Questions

Which languages are supported?

Sixteen: pandas and polars DataFrames, a Python list of dicts, R data.frame and tibble, PHP array, Ruby array of hashes, JavaScript array of objects, a TypeScript typed const with its interface, Go slice of structs, Java List of Maps, C# record array, Swift array of structs, Kotlin data classes, a SQL VALUES list, and JSON5. Each one is written the way somebody who knows that language would write it, not run through one generic printer.

How are strings escaped?

Per language, because the rules differ in ways that matter. PHP and Ruby use single quotes so a value containing $total or #{x} is not interpolated. SQL doubles an embedded quote rather than backslash-escaping it, because that is what the standard says. Everything in the C family uses double quotes with backslash escapes. Getting this wrong does not produce a syntax error, it produces code that runs and gives you the wrong string.

What happens to a column heading that is not a valid field name?

It is renamed by the target language's own convention and the rename is reported. Go and C# get PascalCase, Swift and Kotlin get camelCase, and R back-quotes the original with check.names set to FALSE so R does not mangle it. Go also writes the original heading into a struct tag, so a JSON or CSV round trip through that struct still uses the name you started with. The rename is never silent.

Are the values typed, or is everything a string?

Typed, once per column. A price column becomes floats, a flag column becomes the language's real boolean, and a column of zero-padded product codes stays strings because a leading zero means an identifier. A column mixing 9.99 and 12.50 stays text, because 12.50 as a float prints back as 12.5. Deciding per column rather than per cell is what stops you getting a list with three numbers and one string in it.

What does a blank cell become?

The target language's null: None in Python, nil in Ruby, Go and Swift, null in PHP, JavaScript, TypeScript, Java and Kotlin, NA in R, and NULL in SQL. In the statically typed languages a column with any blank cell is also declared nullable, so TypeScript gets number | null, Go gets a pointer, and C#, Swift and Kotlin get the question mark. A non-null type with a blank in it is a crash waiting for its first run.

Can I limit how many rows go in?

Yes, to the first 10, 50 or 100. Ten thousand literal rows in a source file compiles slowly, breaks most editors' syntax highlighting, and is unreadable in a diff. Past a few hundred rows you want to load the CSV at runtime rather than embed it. When rows are trimmed you are told how many of how many are in the snippet.

Why does pandas get an astype call at the end?

Because pandas infers the object dtype for any column containing a None, and an object column behaves nothing like a float column: arithmetic is slow, comparisons behave oddly, and groupby results differ. Setting the nullable Float64 dtype explicitly for numeric columns gives you a column that keeps its type and its missing values at the same time.

Convert your CSV to code

No sign-up, no upload, no row cap. Sixteen languages, real types, per-language escaping, nullable columns marked.

Back to the converter