Add typed getters and setters over BinaryData properties that represent TypeSpec union types in generated Java models. Use when generated classes expose BinaryData for union-typed fields and you need ergonomic, type-safe accessors instead.
npx skills add https://github.com/Azure/azure-sdk-for-java --skill union-type-wrappers
When the Java codegen encounters a TypeSpec union type (e.g. string | SomeModel), it emits the property as BinaryData. This skill replaces public BinaryData accessors with typed setters and getters for each union variant, following the same pattern established for PromptAgentDefinition.toolChoice.
tsp-location.yaml and pom.xml.TempTypeSpecFiles/. If missing, run tsp-client sync first.Understanding how BinaryData writes to JSON is critical for choosing the correct factory method:
| Factory method | Content type | writeTo(JsonWriter) calls | JSON output |
|---|---|---|---|
| BinaryData.fromString("auto") | StringContent | jsonWriter.writeString("auto") | "auto" (quoted) |
| BinaryData.fromObject("auto") | SerializableContent | jsonWriter.writeRawValue(...) | "auto" (quoted via Jackson) |
| BinaryData.fromObject(42.0) | SerializableContent | jsonWriter.writeRawValue(...) | 42.0 (raw) |
| BinaryData.fromObject(true) | SerializableContent | jsonWriter.writeRawValue(...) | true (raw) |
| BinaryData.fromObject(jsonSerializable) | SerializableContent | jsonWriter.writeRawValue(...) | {...} (JSON object via JacksonAdapter) |
Key: JacksonAdapter has special handling for JsonSerializable types — BinaryData.fromObject() and BinaryData.toObject() both work correctly with Azure JsonSerializable models.
| Union variant | Factory method | Reason |
|---|---|---|
| String value (ID, enum token) | BinaryData.fromString(value) | Writes as JSON string via writeString() |
| Numeric / boolean primitive | BinaryData.fromObject(value) | Writes as raw JSON value |
| Azure JsonSerializable model | BinaryData.fromObject(value) | JacksonAdapter handles serialization |
| Stainless (openai-java) type | BinaryData.fromObject(value) | Jackson handles serialization |
| Union variant | Deserialization | Notes |
|---|---|---|
| String | this.field.toObject(String.class) | Consistent regardless of how BinaryData was created (fromString vs fromObject during deserialization) |
| Primitive (Number, Boolean) | this.field.toObject(Double.class) etc. | Jackson deserializes raw JSON values |
| Azure JsonSerializable model | this.field.toObject(ModelClass.class) | JacksonAdapter calls fromJson() |
| Stainless type | this.field.toObject(StainlessType.class) | Jackson deserializes natively |
Find all BinaryData fields in generated model classes:
grep -rn "private\s\+\(final\s\+\)\?BinaryData\s\+" src/main/java/ --include="*.java"
Exclude List<BinaryData> and Map<..., BinaryData> from this pass — those are collection-of-union patterns that require separate handling.
For each BinaryData property found, determine whether it comes from a union type or an unknown type:
# Search for the property name (use the wire name, e.g. tool_choice, not toolChoice)
grep -rn "<wire_name>" TempTypeSpecFiles/ --include="*.tsp"
type_a | type_b): Proceed with this skill.unknown type: Leave as BinaryData — this is the correct representation.Also check client.tsp for any @@changePropertyType overrides that may have already flattened the union to string (like tool_choice).
For each union type, determine what types the property can hold. Sources:
TempTypeSpecFiles/.OpenAI.*, inspect the Stainless types: jar tf ~/.m2/repository/com/openai/openai-java-core/<version>/openai-java-core-<version>.jar | grep "<TypeName>"
javap -cp <jar> "com.openai.models.responses.<OuterClass>\$<InnerUnion>"
AutoCodeInterpreterToolParam, McpToolFilter).For each union-typed BinaryData property, apply the following pattern:
Do not modify the property declaration. Keep @Generated, the block comment, and the visibility exactly as the codegen produced them. The field is already private — there is nothing to change.
/*
* Original generated block comment.
*/
@Generated
private BinaryData myField;
@Generated.*Internal.// AI Tooling: union type as the first line inside the method body./**
* Get the myField property: original description.
*
* @return the myField value.
*/
BinaryData getMyField() {
// AI Tooling: union type
return this.myField;
}
/**
* Set the myField property: original description.
*
* @param myField the myField value to set.
* @return the MyClass object itself.
*/
MyClass setMyField(BinaryData myField) {
// AI Tooling: union type
this.myField = myField;
return this;
}
For @Immutable classes (value is a constructor param):
BinaryData constructor visibility to package-private (keep for fromJson deserialization).BinaryData getter private./**
* Creates an instance of MyFilter class.
*
* @param type the type value to set.
* @param key the key value to set.
* @param value the value value to set.
*/
MyFilter(MyFilterType type, String key, BinaryData value) {
// AI Tooling: union type
this.type = type;
this.key = key;
this.value = value;
}
public MyFilter(MyFilterType type, String key, String value) {
this.type = type;
this.key = key;
this.value = BinaryData.fromObject(value);
}
Naming convention: set<PropertyName>(<VariantType> value) — use method overloading.
Copy the javadoc from the original generated setter, adapting the @param description to the specific variant type. Add // AI Tooling: union type as the first line inside the method body.
/**
* Set the myField property: original description.
*
* @param myField the string value to set.
* @return the MyClass object itself.
*/
public MyClass setMyField(String myField) {
// AI Tooling: union type
this.myField = BinaryData.fromString(myField);
return this;
}
/**
* Set the myField property: original description.
*
* @param myField the SomeModel value to set.
* @return the MyClass object itself.
*/
public MyClass setMyField(SomeModel myField) {
// AI Tooling: union type
this.myField = BinaryData.fromObject(myField);
return this;
}
When overloading isn't possible (e.g. two different String meanings), disambiguate with parameter names and javadoc.
For List<String> variants:
/**
* Set the allowedTools property: original description.
*
* @param allowedTools the list of tool name strings to set.
* @return the McpTool object itself.
*/
public McpTool setAllowedTools(List<String> allowedTools) {
// AI Tooling: union type
this.allowedTools = BinaryData.fromObject(allowedTools);
return this;
}
Naming convention: get<PropertyName>As<TypeName>()
Copy the javadoc from the original generated getter, adapting the @return description. Add // AI Tooling: union type as the first line inside the method body.
/**
* Get the myField property as a String: original description.
*
* @return the myField value as a String.
*/
public String getMyFieldAsString() {
// AI Tooling: union type
if (this.myField == null) {
return null;
}
return this.myField.toObject(String.class);
}
/**
* Get the myField property as a {@link SomeModel}: original description.
*
* @return the myField value as a SomeModel.
*/
public SomeModel getMyFieldAsSomeModel() {
// AI Tooling: union type
if (this.myField == null) {
return null;
}
return this.myField.toObject(SomeModel.class);
}
For List<String> variants:
/**
* Get the allowedTools property as a list of tool name strings: original description.
*
* @return the allowedTools value as a list of Strings.
*/
@SuppressWarnings("unchecked")
public List<String> getAllowedToolsAsStringList() {
// AI Tooling: union type
if (this.allowedTools == null) {
return null;
}
return this.allowedTools.toObject(List.class);
}
Search for existing code that uses the old BinaryData API:
grep -rn "\.setMyField(BinaryData\|\.getMyField()" src/ --include="*.java"
Update samples, tests, and internal code to use the new typed API. Remove unused BinaryData imports where applicable.
Create a test class per model under src/test/java/.../models/<ModelName>SerializationTests.java.
Each test class must include:
null when the field is not set or absent from JSON.Use these helpers:
private String serializeToJson(MyModel model) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (JsonWriter jsonWriter = JsonProviders.createWriter(outputStream)) {
model.toJson(jsonWriter);
}
return outputStream.toString("UTF-8");
}
private MyModel deserializeFromJson(String json) throws IOException {
try (JsonReader jsonReader = JsonProviders.createReader(json)) {
return MyModel.fromJson(jsonReader);
}
}
mvn compile -Dbuildhelper.addtestsource.skip=true -Dbuildhelper.addtestresource.skip=true \
-Dcodesnippet.skip=true -Dcheckstyle.skip=true -Dspotless.check.skip=true
mvn "-Dtest=*SerializationTests" test \
-Dcodesnippet.skip=true -Dcheckstyle.skip=true -Dspotless.check.skip=true
All tests must pass before finishing.
Before reporting completion, verify:
BinaryData property was classified as union or unknownBinaryData getter/setter made package-private, name kept, @Generated removed, javadoc preserved// AI Tooling: union type placed inside the body of every modified or added getter/setterget*As*()) with javadoc copied from originalBinaryData imports removed from callers| Symptom | Cause | Fix |
|---------|-------|-----|
| BinaryData.fromObject(jsonSerializable) produces wrong JSON | JacksonAdapter not on classpath | Verify azure-core dependency includes JacksonAdapter |
| toObject(AzureModel.class) fails | JacksonAdapter doesn't find fromJson | Use BinaryData.toObject() which delegates to JacksonAdapter.deserialize() — confirm azure-core ≥ 1.51 |
| Setter creates StringContent but expects raw JSON | Wrong factory method | Use fromString() only for string tokens; use fromObject() for primitives and objects |
| Test fails on deserialized value comparison | Asymmetry between fromString/fromObject for string values | Deserialization always uses fromObject(readUntyped()), producing SerializableContent. Use toObject(String.class) in string getters — never toString() — to normalize both paths. |
| Compilation error: cannot find List | Missing import after adding List<String> setter | Add import java.util.List; |
| @SuppressWarnings needed | Unchecked cast on toObject(List.class) | Add @SuppressWarnings("unchecked") to the method |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take azure/union-type-wrappers from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.