Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Fixed the messy auto-generated serde logic in the library

Aperta
#44 0 commenti 1 reazione 1 assegnatario Vedi su GitHub

@mridang ci sta già lavorando.

Dal 7/5/2025.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

Title: Refactor Java SDK Models to Standardize Jackson Usage via ZitadelModel Base Class

Description:
This initiative will refactor the Java SDK's model classes, which already use Jackson, to standardize their implementation, remove boilerplate, and centralize serialization/deserialization logic within a common ZitadelModel base class.

Problem:

While the Java models use Jackson, the current setup likely involves:

  • Boilerplate code within each model (potentially custom equals, hashCode, toString, validation logic that could be handled by annotations).
  • Serialization/deserialization logic potentially scattered, often residing primarily within a central ApiClient or ObjectSerializer class rather than being easily accessible from the model instances themselves.
  • Lack of a common base class to share the configured ObjectMapper and provide consistent ser/des helper methods directly on models.

Impact:

Refactoring will result in:

  • Cleaner and more maintainable model classes, focusing on property definitions and Jackson annotations.
  • Centralized and consistent Jackson ObjectMapper configuration.
  • Easier-to-use models with self-contained serialization/deserialization capabilities.
  • Reduced risk of inconsistencies in how models are handled across the SDK.

Solution / Tasks:

1. Implement ZitadelModel Base Class:
Create an abstract class ZitadelModel. This class will hold a static, shared, pre-configured Jackson ObjectMapper instance and provide convenience methods for serialization and deserialization.

Example ZitadelModel.java (ensure necessary imports):

package com.zitadel.client.model; // Or your chosen SDK package

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.openapitools.jackson.nullable.JsonNullableModule; // Assuming usage

import java.io.IOException;
import java.text.DateFormat; // If using ApiClient.buildDefaultDateFormat()
import java.util.Map;

public abstract class ZitadelModel {

    private static final ObjectMapper defaultObjectMapper = buildObjectMapper();

    private static ObjectMapper buildObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        // Apply existing configurations (crucial)
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
        objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.registerModule(new JsonNullableModule()); // Keep if JsonNullable is used
        // If ApiClient.buildDefaultDateFormat() exists and is needed for java.util.Date:
        // objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat());
        // Add any other necessary configuration
        return objectMapper;
    }

    /**
     * Gets the shared ObjectMapper instance.
     * @return ObjectMapper
     */
    public static ObjectMapper getObjectMapper() {
        return defaultObjectMapper;
    }

    /**
     * Serializes the current object instance to a JSON string.
     * @return JSON string representation
     * @throws JsonProcessingException if serialization fails
     */
    public String toJson() throws JsonProcessingException {
        return getObjectMapper().writeValueAsString(this);
    }

    /**
     * Serializes the current object instance to a Map.
     * @return Map representation
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> toMap() {
        // Convert object to Map using Jackson's convertValue
        return getObjectMapper().convertValue(this, Map.class);
    }

    /**
     * Deserializes a JSON string into an instance of the specified class.
     * @param jsonString The JSON string
     * @param valueType The class of the object to deserialize into
     * @param <T> The type of the object
     * @return Deserialized object
     * @throws IOException if deserialization fails
     */
    public static <T extends ZitadelModel> T fromJson(String jsonString, Class<T> valueType) throws IOException {
        return getObjectMapper().readValue(jsonString, valueType);
    }

    /**
     * Deserializes a Map into an instance of the specified class.
     * @param map The Map
     * @param valueType The class of the object to deserialize into
     * @param <T> The type of the object
     * @return Deserialized object
     */
    public static <T extends ZitadelModel> T fromMap(Map<String, Object> map, Class<T> valueType) {
        // Convert Map to object using Jackson's convertValue
        return getObjectMapper().convertValue(map, valueType);
    }

    // Consider overriding equals, hashCode, and toString here if a standard implementation is desired
    // Or rely on generated implementations using annotations (@JsonIncludeProperties, Lombok, etc.)
}

2. Update OpenAPI Generator Templates for Java Models:
Modify the OpenAPI Generator templates for Java models to:

  • Make generated models extend ZitadelModel.
  • Use standard Java types and Jackson annotations for defining properties.
  • Use @JsonProperty("jsonKey") to map JSON keys to Java field names where they differ (e.g., JSON "userId" vs. Java userId or userId_). Jackson's PropertyNamingStrategies can also be configured on the ObjectMapper for global conversion (e.g., CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES).
  • Remove any custom serialization/deserialization logic previously embedded in the model or its helpers.
  • Ensure enums use @JsonValue or rely on the WRITE_ENUMS_USING_TO_STRING / READ_ENUMS_USING_TO_STRING configuration.
  • Leverage @JsonInclude(JsonInclude.Include.NON_NULL) or other Jackson annotations for fine-grained control if needed (though the global setting often suffices).
  • Use org.openapitools.jackson.nullable.JsonNullable<T> wrapper for fields that need to explicitly distinguish between null and unset, if required by the API design (especially for PATCH).

Target structure for a generated model (e.g., UserServiceUser.java):

package com.zitadel.client.model; // Or your chosen SDK package

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName; // If needed for discriminators later
import java.util.List;
import java.util.Objects;
// Assuming other models (UserServiceDetails etc.) also extend ZitadelModel
// Assuming UserServiceUserState is an Enum configured for Jackson

// Add other necessary annotations like @JsonIncludeProperties, @Generated, etc. if used

public class UserServiceUser extends ZitadelModel {
  @JsonProperty("userId")
  private String userId;

  @JsonProperty("details")
  private UserServiceDetails details;

  @JsonProperty("state")
  private UserServiceUserState state; // Assuming enum

  @JsonProperty("username")
  private String username;

  @JsonProperty("loginNames")
  private List<String> loginNames;

  @JsonProperty("preferredLoginName")
  private String preferredLoginName;

  @JsonProperty("human")
  private UserServiceHumanUser human;

  @JsonProperty("machine")
  private UserServiceMachineUser machine;

  // Constructors (e.g., default, all-args if needed)
  public UserServiceUser() {
    // Default constructor
  }

  // Getters and Setters (standard JavaBeans pattern)
  public String getUserId() { return userId; }
  public void setUserId(String userId) { this.userId = userId; }
  // ... other getters/setters ...

  // Consider standard equals, hashCode, toString (can be generated by IDEs or Lombok)
  @Override
  public boolean equals(Object o) {
      // Standard implementation...
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      UserServiceUser that = (UserServiceUser) o;
      return Objects.equals(userId, that.userId) && Objects.equals(details, that.details) /* && etc. */;
  }

  @Override
  public int hashCode() {
      return Objects.hash(userId, details /* , etc. */);
  }

  @Override
  public String toString() {
      // Standard implementation or use Jackson via toJson()
      try {
          return toJson(); // Or a simpler format
      } catch (JsonProcessingException e) {
          return "UserServiceUser{serialization error}";
      }
  }
}

3. Update SDK Code to Use New Model Methods:

  • Identify where the ApiClient (or equivalent) currently uses its objectMapper.writeValueAsString() and objectMapper.readValue() methods specifically for model objects.
  • Replace these calls with the new methods from ZitadelModel:
    • objectMapper.writeValueAsString(instance) -> instance.toJson().
    • objectMapper.readValue(data, ModelClass.class) -> ModelClass.fromJson(jsonData, ModelClass.class).
    • objectMapper.readValue(data, new TypeReference<List<ModelClass>>(){}) -> Use Jackson's ObjectMapper with TypeFactory or handle list deserialization appropriately, potentially keeping this logic in the client but ensuring it uses the shared ObjectMapper from ZitadelModel.getObjectMapper().
  • The aim is to use the model's own ser/des capabilities where appropriate and ensure the shared ObjectMapper is used consistently.

Expected Outcomes:

  • Java SDK models extend ZitadelModel and primarily contain field definitions and standard Java methods (getters/setters, equals, hashCode, toString).
  • JSON serialization and deserialization logic is centralized in ZitadelModel using a shared, configured Jackson ObjectMapper.
  • Boilerplate related to custom serialization within models is removed.
  • The SDK is easier to maintain and aligns better with standard Java/Jackson practices.
  • Functional equivalence for JSON-based API interactions is preserved.

Additional Notes:

  • Dependencies: Ensure jackson-databind, jackson-datatype-jsr310 (for Java Time), and jackson-databind-nullable (if using JsonNullable) are correctly managed (e.g., via Maven or Gradle).
  • Null Handling: The base ObjectMapper is configured with JsonInclude.Include.NON_NULL. Use @JsonInclude on specific fields/classes if different behavior is needed. JsonNullable<T> should be used for fields requiring explicit null vs. unset distinction.
  • Testing: Thorough testing of serialization/deserialization for various model types is essential.
  • Discriminators: Functionality related to discriminators (@JsonTypeInfo, @JsonSubTypes) is out of scope but should be compatible with this structure if added later.
Lingua principale
Java
Stelle
9
Fork
4
Merge medio
1h 40m
PR unite (30g)
1

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di zitadel/client-java

Tutte le issue di zitadel/client-java

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.