Skip to content

yojo-generator/generator

Repository files navigation

🚀 Yojo — AsyncAPI → Java DTO Generator

YAML-to-POJO for AsyncAPI v2.0/v2.6/v3.0
✅ Polymorphism ✅ Validation ✅ Lombok ✅ Collections/Map ✅ Enums with Descriptions ✅ Inheritance/Interfaces
OpenAPI is not supported (AsyncAPI only)

Yojo Banner

Maven Central Gradle Plugin Portal JDK 17+
License AsyncAPI 2.0+ Documentation
Javadocs GitHub issues GitHub closed issues GitHub pull requests Status


⚠️ Important: Specification Support

Specification Status Notes
AsyncAPI v2.0 / v2.6 ✅ Full Primary target version
AsyncAPI v3.0 (RC) ✅ Experimental Supports operations, channels, messages; payload: { schema: {...} }
OpenAPI 3.x ❌ Not supported Planned no earlier than 2026

💡 For OpenAPI, consider OpenAPI Generator. Yojo is exclusively focused on AsyncAPI.


🧾 Usage Examples (valid YAML ↔ Java)

All examples use valid AsyncAPI-compliant YAML — no abbreviated forms like items: { type: integer }.


1. Collections — correct items structure

YAML

listOfLongs:
  type: array
  items:
    type: integer
    format: int64
    realization: ArrayList
setOfDates:
  type: array
  format: set
  items:
    type: string
    format: date
    realization: HashSet

Java

private List<Long> listOfLongs = new ArrayList<>();
private Set<LocalDate> setOfDates = new HashSet<>();

2. Map with UUID keys

YAML

mapUUIDCustomObject:
  type: object
  format: uuid
  additionalProperties:
    $ref: '#/components/schemas/User'

Java

private Map<UUID, User> mapUUIDCustomObject;

3. Enum with descriptions (x-enumNames)

YAML

Result:
  type: object
  enum: 
    - SUCCESS
    - DECLINE
    - error-case
  x-enumNames:
    SUCCESS: "Operation succeeded"
    DECLINE: "Declined by policy"
    error-case: "Legacy lowercase"

Java

public enum Result {
    SUCCESS("Operation succeeded"),
    DECLINE("Declined by policy"),
    ERROR_CASE("Legacy lowercase");

    private final String value;
    Result(String value) { this.value = value; }
    public String getValue() { return value; }
}

error-caseERROR_CASE, classCLASS_FIELD


5. multipleOf@Digits (preferred over digits)

YAML

bigDecimalValue:
  type: number
  format: big-decimal
  multipleOf: 0.01   # → fraction = 2
bigDecimalValue2:
  type: number
  format: big-decimal
  multipleOf: 10.0   # → integer = 2, fraction = 1

Java

@Digits(integer = 1, fraction = 2)
private BigDecimal bigDecimalValue;

@Digits(integer = 2, fraction = 1)
private BigDecimal bigDecimalValue2;

⚠️ digits: "integer = 2, fraction = 2" is legacy. Prefer multipleOf — it’s standards-compliant, editor-validated, and used for @Digits inference.


6. Polymorphism (oneOf, allOf)

YAML

polymorph:
  oneOf:
    - $ref: '#/components/schemas/StatusOnly'        # { status: string }
    - $ref: '#/components/schemas/StatusWithCode'    # { status: string, code: integer }

Java

public class PolymorphStatusOnlyStatusWithCode {
    private String status;   // from both
    private Integer code;    // only from StatusWithCode (nullable)
}

6.1 Discriminator (discriminator + allOf)

Supports const in discriminator field to override default discriminator value!

YAML — для явной типизации при десериализации

Pet:
  type: object
  discriminator: petType
  properties:
    name:
      type: string
    petType:
      type: string

Cat:
  allOf:
    - $ref: '#/components/schemas/Pet'
  properties:
    huntingSkill:
      type: string

Dog:
  allOf:
    - $ref: '#/components/schemas/Pet'
  properties:
    packSize:
      type: integer

# ✅ const support: discriminator value is "StickBug", not "StickInsect"
StickInsect:
  description: A representation of an Australian walking stick
  allOf:
    - $ref: '#/components/schemas/Pet'
    - type: object
      properties:
        petType:
          const: StickBug
        color:
          type: string
      required:
        - color

Java

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "petType", visible = true)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Cat.class, name = "Cat"),
    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),
    @JsonSubTypes.Type(value = StickInsect.class, name = "StickBug")  // ← const value!
})
public class Pet {
    private String name;
    @JsonTypeId
    private String petType;
}

public class Cat extends Pet {
    private String huntingSkill;
}

public class Dog extends Pet {
    private Integer packSize;
}

// ✅ petType field with const is NOT generated in subtype (inherited from Pet)
public class StickInsect extends Pet {
    private String color;  // only the new field
}

✅ Jackson автоматически десериализует в нужный класс по значению дискриминатора. ✅ Поле-дискриминатор в подтипах НЕ дублируется (наследуется от базового класса). ✅ Поддержка const позволяет использовать значение дискриминатора, отличающееся от имени схемы.


11. Custom Annotations (x-class-annotation, x-field-annotation)

Attribute YAML → Java
x-class-annotation
MySchema:  type: object  x-class-annotation:    - com.example.MyClassAnnotation    - com.example.AnotherAnnotation("param")
@MyClassAnnotation@AnotherAnnotation("param")public class MySchema { ... }
x-field-annotation
properties:  myField:    type: string    x-field-annotation:      - com.example.MyFieldAnnotation      - com.validation.MyConstraint(value = 10)
@MyFieldAnnotation@MyConstraint(value = 10)private String myField;

✅ Supports schema-level and message-level class annotations, plus field-level annotations with parameters.


📦 Integration: YOJO Gradle Plugin (recommended)

yojo {
    configurations {
        create("main") {
            specificationProperties {
                register("api") {
                    specName("test.yaml")
                    inputDirectory(layout.projectDirectory.dir("contract").asFile.absolutePath)
                    outputDirectory(layout.buildDirectory.dir("generated/sources/yojo/com/example/api").get().asFile.absolutePath)
                    packageLocation("com.example.api")
                }
                register("one-more-api") {
                    specName("test.yaml")
                    inputDirectory(layout.projectDirectory.dir("contract").asFile.absolutePath)
                    outputDirectory(layout.buildDirectory.dir("generated/sources/yojo/oneMoreApi").get().asFile.absolutePath)
                    packageLocation("oneMoreApi")
                }
            }
            springBootVersion("3.2.0")
            lombok {
                enable(true)
                allArgsConstructor(true)
                noArgsConstructor(true)
                accessors {
                    enable(true)
                    fluent(false)
                    chain(true)
                }
                equalsAndHashCode {
                    enable(true)
                    callSuper(false)
                }
            }
        }
    }
}

sourceSets {
    main.java.srcDir(layout.buildDirectory.dir("generated/sources/yojo"))
}

tasks.compileJava {
    dependsOn("generateClasses")
}

🔗 Official Gradle Plugin
📦 GitHub


📋 YAML ↔ Java Mapping

Scalar Types

YAML → Java Imports
type: string, format: uuid UUID java.util.UUID
type: object, format: date LocalDate java.time.LocalDate
type: string, format: local-date-time LocalDateTime java.time.LocalDateTime
type: object, format: date-time OffsetDateTime java.time.OffsetDateTime
type: number, format: big-decimal, multipleOf: 0.01 BigDecimal java.math.BigDecimal, @Digits(integer = 1, fraction = 2)

Collections and Maps

YAML → Java
type: arrayitems:  type: string
List<String>
type: arrayformat: setitems:  type: integer
Set<Integer>
type: objectadditionalProperties:  type: string
Map<String, String>
type: objectformat: uuidMapadditionalProperties:  $ref: '#/components/schemas/User'
Map<UUID, User>
type: objectadditionalProperties:  type: array  format: set  items:    type: string
Map<String, Set<String>>

🧰 Supported Attributes (per Dictionary.java)

Attribute Where Type Example Generation
realization items, additionalProperties string realization: ArrayList = new ArrayList<>()
primitive field boolean primitive: true int, boolean, long
multipleOf number number multipleOf: 0.01 @Digits(integer = 1, fraction = 2)
digits number string digits: "integer=2,fraction=2" @Digits(...) (legacy)
validationGroups schema list validationGroups: [Create.class] @NotBlank(groups = {Create.class})
validationGroupsImports schema list validationGroupsImports: [pkg.Validation] import pkg.Validation;
validateByGroups schema list validateByGroups: [name, email] annotations only on listed fields
lombok.* schema/message nested see Gradle config @Data, @Accessors, @AllArgsConstructor
extends, implements schema/message fromClass, fromPackage, fromInterface see examples extends X implements Y
format: interface schema interface X { ... }
format: existing field name, package import, no class gen
pathForGenerateMessage message.payload string io.github.events custom message package
removeSchema message.payload boolean removeSchema: true skip DTO gen for $ref target
x-enumNames enum map SUCCESS: "Ok" enum + description field
x-class-annotation schema, message list [com.example.MyAnnotation] class-level annotations
x-field-annotation field list [com.example.MyAnnotation("value")] field-level annotations

🗺 Roadmap (2025–2026)

Feature Status Description
Discriminator ✅ Done @JsonTypeInfo, @JsonSubTypes for polymorphism
@JsonTypeId on fields ✅ Done Add @JsonTypeId to discriminator field in subtypes
Discriminator const support ✅ Done Support const in discriminator field to override default value
Jackson annotations 🟡 Partial @JsonProperty, @JsonInclude done
AsyncAPI spec validation 🚧 Planned Validate $ref, type, format, circular refs
Lombok extensions 🚧 Planned @Builder, @Singular, @SuperBuilder
OpenAPI 3.1 support 🚧 Planned for Q2 2026 Only after AsyncAPI 3.0 stabilization
Freemarker templates 🚧 Planned For enterprise customizations

📬 Contact

🙌 Send your contracts — I’ll make a PoC.
🐞 PRs and issues are welcome!


⚖️ License

Distributed under the Apache License 2.0.
See LICENSE.md.


🚀 Let’s generate some code!
AsyncAPI → Java — fast, precise, zero manual work.


Packages

 
 
 

Contributors

Languages