I've just started looking at this exact problem for my company's Axon code base. Still in the prototyping/experimentation stage, but I'll describe what I'm planning to do.
We can't afford to have our application offline for long enough to do a stop-the-world migration of the existing events; we need to do the migration while the application is running with as close to zero downtime as possible. We're still using Axon 2 at the moment, but the same general principles, I think, should apply in Axon 3 as well.
I'm going to say "event store" instead of "event store and snapshot event store and saga store" but you'll almost certainly want to do all of them so you don't have XML-based sagas and JSON-based events in the same application. The same approach applies to all of them.
1. Make sure all your event classes serialize and deserialize correctly with Jackson or whatever JSON serializer you're going to use. XStream can handle some things that Jackson can't.
2. Create an event store class that wraps other underlying event stores. This is pretty straightforward but requires looking through some of Axon's internal APIs. For any write operation, it needs to perform that operation on all the underlying stores, and for read operations, you need to be able to configure which underlying store it uses.
3. Add a new table to hold the JSON-serialized payloads. We're using PostgreSQL, so we create this with payload and metadata columns of type JSONB so we can use PostgreSQL's JSON features for ad-hoc queries against the event store (which is the main point of the exercise for us).
4. Create a new event store class that writes to the newly-added table. Configure it with a Jackson serializer, and then configure the wrapper store from step 1 to wrap both this new event store and the original XML-based one.
5. Like Steven van Beelen says, write a migration utility that goes through the old XML-based event table and, for any event that doesn't already exist in the new table, deserializes the XML, serializes the object as JSON, and inserts the equivalent row in the new table. (Metadata too.)
6. Deploy the code from steps 1-3 and configure the wrapper event store to read from the XML event table. At this point you will be writing two copies of each event, but only the XML version will be read.
7. Run the migration utility from step 4. Other than additional database load, this shouldn't have any impact on the running application since nothing is reading the JSON data yet.
8. Once you're satisfied the events have been migrated correctly, configure the wrapper event store to start reading JSON instead.
9. Once you're satisfied that you won't want to roll back to use the XML-based events, delete the wrapper event store and the XML-based event store, and let Axon directly use the JSON event store.
10. Drop the XML table.
That's the plan. A bit complicated but so far I don't see a reason it won't work.
-Steve