Avro to JSON Converter
Drop an .avro file and read what is actually in it. The container is decoded in your browser, nested records stay nested, and you choose a JSON array or JSON Lines. No upload, no Python, no signup.
Want to filter or reshape the records afterwards? Open the app
Where the .avro file usually came from
Nobody writes Avro by hand. A file shows up because a pipeline produced one, and you are the person who has to say what is inside it before the standup starts.
- A Kafka topic dump. The S3 sink connector writes each partition out as .avro on a schedule. When somebody asks what yesterday's events looked like, those files are the answer, and none of them opens in a text editor.
- A data lake landing zone. Spark and Flink jobs often write Avro on the way in and Parquet on the way out. The raw copy is the one that still has the original field names.
- A ticket attachment. Another team drops a 4 MB file into a bug report with the words the payload looks wrong. You need to see the payload before you can agree.
- Fixtures for a consumer you are writing. Before your service reads the topic for real, one honest record beats a schema diagram.
- A schema you were never sent. The writer schema travels inside the container, so the file is its own documentation. Reading it as JSON is the quickest way to get the field list.
All of those end the same way: you want to look at the records, and the standard advice is to install a JVM, or pip install fastavro, or spin up a notebook. This page is the shortcut for the times you just need to see the thing.
Worked example: two order events
Here is the schema sitting in the header of a file called orders-2026-01-14.avro, trimmed to fit:
{"type":"record","name":"OrderPlaced","namespace":"com.shop.events","fields":[
{"name":"order_id", "type":"long"},
{"name":"placed_at", "type":{"type":"long","logicalType":"timestamp-millis"}},
{"name":"ship_by", "type":{"type":"int","logicalType":"date"}},
{"name":"amount_due", "type":{"type":"bytes","logicalType":"decimal","precision":9,"scale":2}},
{"name":"seats", "type":{"type":"bytes","logicalType":"decimal","precision":4,"scale":0}},
{"name":"customer", "type":{"type":"record","name":"Customer","fields":[
{"name":"id","type":"int"},{"name":"country","type":"string"}]}},
{"name":"items", "type":{"type":"array","items":"string"}},
{"name":"coupon", "type":["null","string"],"default":null}
]}
Two records went in. This is the output, character for character, with the widget on its default settings of Pretty and JSON array:
[
{
"order_id": 90211,
"placed_at": "2026-01-14T09:12:04.000Z",
"ship_by": "2026-01-17",
"amount_due": "129.99",
"seats": 3,
"customer": {
"id": 4471,
"country": "IE"
},
"items": [
"desk-lamp",
"shade"
],
"coupon": "SPRING10"
},
{
"order_id": 90212,
"placed_at": "2026-01-14T09:12:57.000Z",
"ship_by": "2026-01-16",
"amount_due": "-45.50",
"seats": 1,
"customer": {
"id": 118,
"country": "PT"
},
"items": [],
"coupon": null
}
]
Four details in there are worth a second look, because they are the ones people file bugs about.
- Logical types are resolved, not left raw. On the wire, placed_at is the integer 1768381924000 and ship_by is the integer 20470. They come out as an ISO 8601 timestamp and a plain calendar date. A time-millis or time-micros field arrives as a clock time in the same spirit.
- A decimal with a scale is a string. amount_due was stored as the unscaled integer 12999 with scale 2, and it reads back as "129.99" in quotes. The negative one keeps its trailing zero, "-45.50", because the scale says two digits. seats is a decimal with scale 0, so there is no fraction to lose and it arrives as the number 3.
- The nested record stayed a nested object. customer is an object with its own two keys, exactly as the schema describes. Nothing is flattened, prefixed or renamed.
- A null union is just null. The second order has no coupon, so the value is null rather than the wrapped shape some Avro libraries hand back. An empty array is an empty array, not a null.
Switch Structure to JSON Lines and the same two records come back one per line, which is the shape most loaders and log tools want:
{"order_id":90211,"placed_at":"2026-01-14T09:12:04.000Z","ship_by":"2026-01-17","amount_due":"129.99","seats":3,"customer":{"id":4471,"country":"IE"},"items":["desk-lamp","shade"],"coupon":"SPRING10"}
{"order_id":90212,"placed_at":"2026-01-14T09:12:57.000Z","ship_by":"2026-01-16","amount_due":"-45.50","seats":1,"customer":{"id":118,"country":"PT"},"items":[],"coupon":null}
The download name follows the choice: orders-2026-01-14.avro comes back as orders-2026-01-14.json in array mode and orders-2026-01-14.jsonl in lines mode. The Formatting toggle applies to the array only, since indenting JSON Lines would break the one record per line contract it exists for.
What the reader does with the file
An .avro file is an Object Container File, and the layout is simple enough to describe in a paragraph. It opens with four magic bytes, Obj and a version byte. Then comes a small map of metadata holding two entries that matter: avro.schema, which is the writer schema written out as JSON, and avro.codec, which names the compression. After that sits a random 16 byte sync marker.
The rest of the file is blocks. Each one says how many records it holds and how many bytes long it is, then carries the records packed end to end, then repeats the sync marker so a reader that lost its place can find the next boundary. The decoder walks those blocks, decompresses each one if the codec calls for it, and decodes records against the schema from the header. That is why you never need the .avsc file: it is already inside.
Two codecs are handled here, null and deflate, and they produce byte identical JSON from the same records. Anything else stops with the codec named in the message, for example a snappy file reporting that only null and deflate are supported and asking you to re-encode. The reader library is fetched only once a file is in the box, so landing on this page costs you nothing.
Limits worth knowing before you drop a file
- Schema evolution is not applied. There is no reader schema and no registry lookup here. Records are decoded with the schema in that file's own header, which is the writer's view of the world on the day it was written. If a field was added last month, compare two files side by side rather than expecting one to be projected onto the other.
- It reads containers, not bare payloads. A single Avro encoded message pulled off a topic, with no header and no schema, is not a file this page can open. It stops immediately and says the header does not identify it as Avro. That check reads the first four bytes, so it fails fast rather than half way through.
- The name has to end in .avro. The picker filters on the extension, with a fallback for files whose browser reported type is a generic binary stream. Pipeline output named part-00000 with no suffix is worth renaming before you drop it.
- Everything is decoded before anything is shown. There is no streaming preview. A 90 MB container will hold both the decoded records and the JSON text in memory at once, and the JSON is normally the larger of the two.
- A container whose schema is not a record still works. A file written with a bare string schema comes back as a JSON array of strings, one entry per value. It is unusual, and it does not error.
- 100 MB is the ceiling on this page. No record cap, no metering, no daily allowance. Past 100 MB the widget hands you to the full editor, which streams instead.
Frequently Asked Questions
Is my .avro file uploaded anywhere?
No. The container is decoded by JavaScript running in your tab, and there is no upload endpoint behind this page. The Avro decoder is a small script chunk served from this site, loaded the first time you drop a file and never sent anything in return. Nothing is stored between visits, so a reload gives you an empty box.
How large an Avro file can it take?
100 MB. Past that the widget offers the full editor instead, which streams the read rather than holding everything at once. Below the ceiling, remember that every record is decoded before any JSON appears, and the JSON text is usually several times the size of the Avro it came from. Files over 500,000 records still convert and add a note saying how many were loaded.
What happens to nested records?
Nothing. That is the reason to convert to JSON rather than CSV. A record field becomes a nested JSON object, an array field stays an array, and a map becomes an object keyed the same way. If you want one flat row per record instead, with dotted column names, use the Avro to CSV page.
Which compression codecs are supported?
The null codec (uncompressed) and deflate. Both produce identical JSON from identical records. A file written with snappy, bzip2, xz or zstandard stops with a message naming the codec it found and asking you to re-encode, because those decompressors are not shipped to the browser. Avro tooling defaults vary, so check your writer config if you are choosing.
Why did a decimal come back in quotes?
Because a decimal with a scale above zero is written out as a string, so the digits survive exactly. An amount stored as 12999 with scale 2 becomes the string 129.99 rather than a float that might print as 129.99000000000001. A decimal with scale 0 has no fraction to protect, so it arrives as a plain JSON number.
Do I need the .avsc schema file as well?
No. An Avro Object Container File carries the writer schema in its own header, as JSON, which is what the decoder reads. That is also why an .avro file is worth more than a raw payload when someone hands you one with no context: the field names and types are inside it.
Related
Read your Avro file
Drop the .avro in, pick an array or JSON Lines, then copy the result or download it. No install, no signup, no record cap.
Back to the converter