Kafka Avro JSON Converter

Convert between the Avro JSON encoding and plain JSON, in either direction, against a schema. The difference is union wrapping, which is what the console producer and the REST proxy insist on and what a hand-written line gets wrong.

Detection tries the Avro form first, because that is the form the wrapping exists to disambiguate. A document that is valid as both is reported rather than resolved.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Convert. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

An unwrapped union

Plain JSON that the console producer rejects, because Avro JSON wraps union values in their type

{"id":1,"email":"a@b.com"}

Already in Avro JSON

The same record with the union wrapped, which is what Avro tooling expects

{"id":1,"email":{"string":"a@b.com"}}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Writing a union value unwrapped

    Avro JSON wraps a non-null union value in its type name. Plain JSON is rejected by the console producer with a message about the union.

    Instead:Wrap it: {"string":"value"}, or null for the null branch.

  2. Assuming Avro JSON is ordinary JSON

    Bytes are encoded as ISO-8859-1 strings and unions are wrapped. Round-tripping through a normal JSON tool corrupts both.

    Instead:Use the Avro JSON encoding when talking to Avro tooling, and plain JSON everywhere else.

  3. Omitting a field that has a default

    Avro JSON requires every field to be present, defaults included. Defaults apply to schema resolution, not to the encoding.

    Instead:Write every field.

Two JSON encodings, and the one difference between them

Avro defines its own JSON encoding, and it is not the JSON you would write by hand. The whole difference is union wrapping, and it is the reason kafka-avro-console-producer rejects a line that looks perfectly correct.

A union value is wrapped in its type name

In the Avro JSON encoding every non-null union branch is a one-key object named for the branch's type: {"string": "abc"}. Only the null branch is written bare, as null. That is what makes a union unambiguous in JSON, since a plain value cannot say which branch was meant when two branches share a JSON shape.

// Avro JSON
{"note": {"string": "hi"}}

// plain JSON
{"note": "hi"}

A record, enum or fixed branch uses its fullname

This is where a hand-written line usually goes wrong. The key is com.example.orders.Money, not Money, and the parser's error is about an unknown union branch rather than about the name being short. Anything without a namespace uses the bare name, which is why it works in a tutorial and stops working the moment a namespace is added.

Bytes and fixed are strings of code points

Both are written as a JSON string whose code points are the byte values, so a byte of 0xFF is the character U+00FF. That is the specification, and it means a payload of arbitrary bytes round-trips through JSON while looking like nonsense. Both forms on this page do the same thing here, so the plain form and the Avro form differ in union wrapping and in nothing else.

Some unions have no plain form at all

A union of int and long, or of two records, has branches that share a JSON shape, so converting from plain JSON means guessing which was meant. This page picks the first branch that fits and says so rather than doing it quietly. If a document like that is going anywhere near a producer, it has to be in the Avro form.

A field with a default may be omitted from JSON, and never from binary

Reading JSON, a missing field with a default is filled in. In the binary encoding there is nothing to omit: every field is written in order with no names and no lengths, so a value has to exist for each one whether or not the schema has a default. That asymmetry catches people moving between the two.

What this cannot see

Whether the schema is the right one. Both forms are checked against the schema you paste and nothing else, so a document that converts cleanly is well-formed for that schema and says nothing about whether that is the schema on the topic. Everything runs in your browser.