How to synthesize / codegen a single JSON file

Hi folks :wave:,

I’m trying to do some code generation as part of tuist generate. I’ve managed to get it “sort of” working, but it feels like I’m definitely not using best practice here. I’d really appreciate any tips / suggestions / pointers on where to find the relevant docs.

I’ve read through Synthesized files · Projects · Develop · Guides · Tuist, had a look at some fixtures, and checked out the API reference but I still can’t figure things out :sweat_smile:.

For context, I’ve got a JSON file that defines a bunch of feature flags. I’d like to do some code generation to create an enum for the keys to ensure type safety (amongst some other things).

To do this I’ve:

  • Placed a JSON.stencil in Tuist/ResourceSynthesizers
  • Updated the relevant Project.swift file to include my JSON as a resource
  • Updated the resourceSynthesizers property for the project to include .json()

The main issue I’ve spotted so far is that, if I include a .json() synthesizer in any of my other projects, the same stencil is run - which I don’t want. I’ve worked around this in the .stencil file itself by checking the filename, but this doesn’t seem correct.

Is there a recommended way to “scope” the JSON synthesizer that I’m missing? Ideally I’d like to be able to say something like “use this stencil for this file(s)”. Or, alternatively, is there an entirely different approach I should be using here?

I’m not currently using .JSON() synthesizers for anything else, but I don’t want to introduce something that’s going to cause massive headaches in future if I do!

Thank you :pray:!

Hey @CloudosaurusRex01!

The resource synthesizers have been designed to convert files with a specific extension to a Swift file. I’d be curious why you need different templates for different JSON files? Shouldn’t a synthesization of an arbitrary JSON produce a Swift file with a consistent structure?

Instead of using the filename, you could consider distinguishing your files using a different file extension. This is an example using that API.

Thanks @marekfort! I’ll look into this API.

To answer your question around why I want a different template for different JSON files - it’s primarily because I’ve got a specific JSON file where I want to do some extra code generation.

So, rather than just translate the JSON into an easy-to-access, consistent Swift structure, I want to generate some new enum types and populate cases based on the content of the JSON file. These types would then be consumed by a different interface that acts as a wrapper.

The reason for trying to code gen this (rather than create a new Codable enum or similar) is because this JSON file would ideally be a shared, multi-platform source of truth and I’d like to reduce the maintenance overhead as much as possible. (e.g., rather than have to update a Swift model / enum, a developer would simply update the JSON file, run tuist generate, and then they’d have access to a new enum case potentially. This extra processing definitely wouldn’t be necessary for other, more generic JSON files where the Swift output would essentially just be “easy accessors”.

For similar reasons, I was hoping to integrate this as part of tuist generate, rather than an extra build step or script within Xcode - meaning that the project is “ready to go” as soon as the Xcode project is opened.

Am I perhaps trying to use synthesizers for something other than what they were intended, or is there a different tool you’d recommend for this?

Yeah, the synthesizers are meant to synthesize code for files that you would include as resources. However, what you’re trying to do is not that.

What you would need is a pre- and post-generation hook – however, that’s not something that’s implemented, yet.

The alternatives are:

  • Wrap the generation in something like a Mise task and generate the Swift files from your JSONs as part of it. You would then run mise run generate instead of tuist generate
  • Generate files as build phases – if that would make sense in this case.

Thanks @marekfort - I appreciate the reply :pray:! I’ll have a look at those options and determine what’s best for my project.

1 Like

Hey @marekfort - I’ve been exploring the app_with_plugins fixture and am getting some odd behaviour. Not sure if I’ve missed a step somewhere.

Here’s what I’ve done:

  • Added a JSON file to the Resources folder
  • Added LocalPlugin/ResourceSynthesizers/JSON.stencil and entered some dummy content
  • Modified the existing LocalPlugin/ResourceSynthesizers/Strings.stencil to add some extra content
  • Updated Project+Templates.swift to include .json(plugin: "LocalPlugin") alongside the existing .strings(plugin: "LocalPlugin")
  • Run tuist generate

Expected outcome:

  1. To see my changes to the generated strings in the Derived folder
  2. To see the new JSON file in the target’s resources
  3. To see a new TuistJSON+TuistPluginTest.swift or similar Derived folder, alongside TuistStrings+TuistPluginTest.swift

I’m seeing (1) and (2), but not (3).
It’s like the JSON.stencil file hasn’t been picked up at all, but I can see the changes I’ve made in Strings.stencil have been reflected as expected.

I should add that, if I replace .json(plugin: "LocalPlugin") with .json() and then move the .stencil to the project’s (not plug-in’s) ResourceSynthesizers folder, then I get the expected result. Was hoping to use a plug-in for this task though!

Have I missed a crucial step here :see_no_evil:?

I’m seeing this on Tuist 4.40.0 and also 4.33.0 (the same used by my project at the moment).

Thanks for your help :pray:!