new Block("mymod:mystone").setShape(Shapes.CUBE).setTexture("mymod:stone_texture").setStepSound(Sounds.STEP_ON_STONE)...
you now have to do some of this inline (the part that can't be customized in data packs):
new Block("mymod:mystone").setStepSound(Sounds.STEP_ON_STONE)...
but the rest is looked up across 5 different cross-referenced JSON files full of magic values with no autocomplete or syntax highlighting. Start with an indirection layer in assets/mymod/blockstates/mystone.json: [2]
{"variants": {"": {"model": "mystone"}}}
then of course you have to actually specify how to display the block: [3]
{"parent": "minecraft:block/cube_all", "textures": {"all": "fabric-docs-reference:block/steel_block"}}
(you see that? there are inheritance and variables in Minecraft's ad-hoc JSON language)
You need a second file to specify how to display the item when it's held in your hand. Usually it's similar boilerplate. But have a look at the abomination that is "item property overrides" [4] [5] as an example of inner-platforming. Instead of render(is_cast ? cast_model : uncast_model); there's this whole infrastructure of a registry of item property predicates written in Java which can then be referenced in JSON to select a different model file under specific conditions.
[1] https://thedailywtf.com/articles/the_inner-platform_effect
[2] https://docs.minecraftforge.net/en/1.12.x/models/blockstates...
[3] https://docs.fabricmc.net/develop/data-generation/block-mode...
[4] https://docs.minecraftforge.net/en/1.12.x/models/overrides/
[5] https://minecraft.fandom.com/wiki/Tutorials/Models#Example:_...
Yes, having to declare json files for your new block in your mod is a pain...
Meanwhile what it was built for, resource packs, this actually gives a good amount of power to the pack maker without having to ask the client to run untrusted java code.
[1] https://mikehadlow.blogspot.com/2012/05/configuration-comple...
not even separate textures are necessary, you could very well use atlases and just stitch them together at runtime with rectpacking and just remap the input texcoords to the new, bigger atlas... boom, mod support with atlases without creating 400 2KB .png's in the game folder.
similarly, blocks can be done in code, and modders can either use that, and optionally you can expose the same API in LUA or whatever if you need a less involved / sandboxed version of mods which can be downloaded from a server or whatever.
Here's an example of shit being done from code, it's fairly terse and you don't need to trawl through 7 files to do anything: (yes I know it doesn't have i18n yet but that won't make it much more complex either)
SHORT_GRASS = register(new Flower(Blocks.SHORT_GRASS, "Short Grass"));
SHORT_GRASS.setTex(crossUVs(8, 1));
SHORT_GRASS.setModel(BlockModel.makeGrass(SHORT_GRASS));
SHORT_GRASS.transparency();
SHORT_GRASS.shortGrassAABB();
SHORT_GRASS.noCollision();
SHORT_GRASS.waterTransparent();
These are fluent/chainable so I could have put all of them on one line but that's less readable IMO, but your choice really.For data files (textures, sounds and other assets) you could use a virtual filesystem like Quake did (PhysFS is a good library which vaguely approximates that) and get rid of the stupid amount of folder nesting specifying behaviour, you can just have toplevel folders and use modloader order to disambiguate.
tl;dr: almost anything can be made to work with the most convenient/most sensible method of making stuff instead of using a bunch of awkward and convoluted JSON files you aren't even using! (MC internally generates the JSONs from code, so the data lives through a code -> JSON -> code roundtrip, they aren't even dogfooding their own format lol)