Specification scope: Core only.
Tool: Structurize is the JSON Structure-focused command-line interface and conversion toolkit from the Avrotize project.
Suppose a shipment acquires the handling tag fragile twice: once from the
product catalog and once from a warehouse rule. A list retains both copies and
exposes their positions. Code can then start counting tags or depending on
arrival order, even though neither behavior belongs to the fulfillment
contract.
JSON uses an array for both collections, so the value alone does not distinguish
a list from a set. JSON Structure declares handlingTags as a set;
that schema requires unique elements and treats their order as insignificant.
The Structurize outputs tested below map that declaration to set-like collections in five language targets. Protocol Buffers and an unconstrained SQL table can carry the values but do not enforce uniqueness. A projection must either preserve the set semantics or identify the boundary where validation restores them.
Declare membership at the source
The fulfillment contract uses handling tags to activate independent processing rules. A tag is either present or absent. Its position has no significance.
{
"$schema": "https://json-structure.org/meta/core/v0/#",
"$id": "https://example.com/schemas/shipment-plan",
"name": "ShipmentPlan",
"type": "object",
"properties": {
"orderId": { "type": "uuid" },
"handlingTags": {
"type": "set",
"items": {
"type": "string",
"enum": ["fragile", "keep-dry", "temperature-controlled"]
}
}
},
"required": ["orderId", "handlingTags"],
"additionalProperties": false
}
The JSON representation still uses an array:
{
"orderId": "c28788fa-73bd-4b39-a209-2f75f82c6653",
"handlingTags": ["fragile", "keep-dry"]
}
Under the core set rules, elements are unique and order is insignificant. Repeating
"fragile" makes the instance invalid. Reversing the two elements does not
change its data-model meaning.
That differs from an array, where order is significant and duplicate elements are
allowed. Both use square brackets. Only the schema tells a consumer which
operations belong to the model.
Native collections preserve the intent
Structurize’s code generators can carry a JSON Structure set into native collection types. For this property, the relevant mappings are:
- C#:
HashSet<T>(converter, tests) - Java:
Set<T>(converter) - TypeScript:
Set<T>(converter) - Rust:
HashSet<T>(converter) - Go:
map[T]bool(converter)
Generate those bindings from the same source contract:
structurize s2cs shipment-plan.struct.json --out generated/dotnet --namespace Fulfillment.Contracts
structurize s2java shipment-plan.struct.json --out generated/java --package com.example.fulfillment
structurize s2ts shipment-plan.struct.json --out generated/typescript --package fulfillment-contracts
structurize s2rust shipment-plan.struct.json --out generated/rust --package fulfillment_contracts
structurize s2go shipment-plan.struct.json --out generated/go --package fulfillment
For a complex generated project and its full file tree, see the prebuilt Avrotize Inventory to C# gallery example. For this small schema, the TypeScript enum below is the single representative multi-file code-generation output shown here.
The exact API idiom varies. In C#, adding "fragile" twice to a
HashSet<string> leaves one member. Java sets, Rust’s HashSet<String>, and
Go’s membership map express the same basic operation: test whether the value
is present. The generated TypeScript property is
Set<HandlingTagsSetEnum>, which would provide the same membership behavior,
but this particular generated project is not usable as emitted. Structurize
writes the string values as bare enum member names:
Generated output: HandlingTagsSetEnum.ts
/** A HandlingTagsSetEnum enum. */
export enum HandlingTagsSetEnum {
fragile = "fragile",
keep-dry = "keep-dry",
temperature-controlled = "temperature-controlled"
}
npm run build rejects the hyphenated members, beginning with TS1357: An enum
member name must be followed by a ',', '=', or '}'. The intended Set<T>
shape is visible in the generated source, but TypeScript cannot enforce it
until the generator produces legal enum identifiers.
Native set APIs also omit positional access. Code must define an ordering before it can ask for “the first handling tag,” because the source contract defines no first tag.
JSON preserves values, not set behavior
Serialization crosses into a representation with fewer collection types. JSON has arrays, not a distinct set token, so a serializer emits a sequence of values. A receiver that reads the payload without the schema sees only an array.
This creates two contract boundaries.
First, JSON Structure assigns no meaning to set order. Unless another protocol defines an order, serializers can produce either of these payloads:
{"handlingTags":["fragile","keep-dry"]}
{"handlingTags":["keep-dry","fragile"]}
They carry the same set. Byte-wise comparison, byte-wise signatures, snapshots, and cache keys will see different strings unless the surrounding protocol adds a canonical ordering rule. JSON Structure’s set declaration does not create that rule because order is outside the value’s meaning.
Second, a generic decoder may materialize a list and accept duplicates. A schema-aware boundary must validate uniqueness or construct the target set in a way that detects duplicate input. Silently collapsing duplicates can hide a producer defect. Rejecting them preserves the contract and gives the producer a useful failure.
Some projections lose the distinction
Protocol Buffers has repeated fields rather than a set field. Project the
schema with the exact s2p command:
structurize s2p shipment-plan.struct.json --out generated/proto
The target shape can carry several tag values, but repeated does not itself
promise uniqueness or erase ordering. The source set semantics therefore
need validation around the generated protocol boundary. A downstream user who
sees only the .proto cannot recover that promise from the repeated field:
Generated output: proto.proto
syntax = "proto3";
package proto;
message ShipmentPlan {
enum HandlingTagsItemEnumEnum {
fragile = 0;
keep-dry = 1;
temperature-controlled = 2;
}
string orderId = 1;
repeated HandlingTagsItemEnumEnum handlingTags = 2;
}
This is the exact generated fragment, and it has a second defect:
grpc_tools.protoc rejects the two hyphenated enum members with Missing
numeric value for enum constant. The source-to-Proto command succeeds, but
the emitted schema does not compile for these enum values.
Tabular projections have a related mismatch. Run the direct SQL projection
with s2sql:
structurize s2sql shipment-plan.struct.json --out generated/shipment-plan.sql --dialect postgres
Generated output: shipment-plan.sql
CREATE TABLE "ShipmentPlan" (
"orderId" UUID,
"handlingTags" JSONB,
PRIMARY KEY ("orderId", "handlingTags")
);
COMMENT ON COLUMN "ShipmentPlan"."handlingTags" IS '{"schema": {"type": "set", "items": {"type": "string", "enum": ["fragile", "keep-dry", "temperature-controlled"]}}}';
A tabular list of tag values does not inherently retain JSON Structure’s set
meaning. A relational design can enforce uniqueness with a key or unique
constraint over the shipment and tag columns, but that is a target-specific
representation decision. Without such enforcement, the table can hold two
fragile rows even though the source contract rejects the corresponding JSON
instance.
Proto repeated fields and unconstrained SQL rows do not preserve set uniqueness. Enforce it at those boundaries or record the loss.
Test uniqueness and order at each boundary
A set projection review answers four questions:
- Does the generated in-memory type prevent duplicate membership?
- Does deserialization reject duplicate input instead of quietly normalizing it?
- Does any byte-sensitive operation define a canonical element order outside the set contract?
- Does a narrower target, such as Proto or a tabular list, enforce uniqueness elsewhere?
Those answers belong in projection tests. Feed duplicate tags to each inbound adapter and expect rejection. Serialize the same logical set after different insertion orders and avoid asserting incidental byte order unless the protocol defines one. Insert duplicate membership into the storage projection and verify the selected database policy.
Replacing a set with a list adds position and permits duplicate values.
Application code can then depend on behavior that the source contract does not
define. Keep handlingTags as a set where the target supports one. For targets
that do not preserve set semantics, enforce uniqueness at the boundary and
document any ordering required by the surrounding protocol.