Skip to content

Go

PolyXML provides high-throughput native XML parsing for Go, replacing the standard library's reflection-heavy encoding/xml with a zero-copy Rust streaming engine connected via Cgo.


📦 Installation

go get github.com/polyxml/PolyXML/bindings/go

Requirements: Cgo enabled (CGO_ENABLED=1) and libpolyxml installed or present in library search path.


1. Schema Definition & Basic Parsing

Use NewSchemaBuilder to declare attributes, child elements, and scalar types:

package main

import (
    "fmt"
    "log"

    "github.com/polyxml/PolyXML/bindings/go"
)

func main() {
    // 1. Build schema
    builder, err := polyxml.NewSchemaBuilder("ServerNode")
    if err != nil {
        log.Fatalf("Failed to create builder: %v", err)
    }

    builder.AddField("node_id", "id", polyxml.FieldAttribute, polyxml.ScalarInt)
    builder.AddField("hostname", "hostname", polyxml.FieldElement, polyxml.ScalarString)
    builder.AddField("ip_address", "ip", polyxml.FieldElement, polyxml.ScalarString)
    builder.AddField("is_active", "active", polyxml.FieldElement, polyxml.ScalarBool)
    builder.AddField("load_average", "load", polyxml.FieldElement, polyxml.ScalarFloat)

    schema, err := builder.Build()
    if err != nil {
        log.Fatalf("Failed to build schema: %v", err)
    }

    xmlPayload := []byte(`
        <ServerNode id="42">
            <hostname>worker-us-east-1</hostname>
            <ip>10.0.1.42</ip>
            <active>true</active>
            <load>0.68</load>
        </ServerNode>
    `)

    // 2. Deserialize XML bytes
    val, err := polyxml.Deserialize(xmlPayload, schema)
    if err != nil {
        log.Fatalf("Deserialization error: %v", err)
    }

    // 3. Extract typed fields
    id, _ := val.GetField("node_id").GetInt()
    host, _ := val.GetField("hostname").GetString()
    ip, _ := val.GetField("ip_address").GetString()
    active, _ := val.GetField("is_active").GetBool()
    load, _ := val.GetField("load_average").GetFloat()

    fmt.Printf("Node #%d [%s (%s)]: Active=%v, Load=%.2f\n", id, host, ip, active, load)
}

2. Formatting & Serialization

Serialize native values into formatted XML byte slices:

package main

import (
    "fmt"
    "log"

    "github.com/polyxml/PolyXML/bindings/go"
)

func main() {
    builder, _ := polyxml.NewSchemaBuilder("Message")
    builder.AddField("id", "id", polyxml.FieldAttribute, polyxml.ScalarInt)
    builder.AddField("text", "text", polyxml.FieldElement, polyxml.ScalarString)
    schema, _ := builder.Build()

    xml := []byte(`<Message id="77"><text>Cluster sync complete</text></Message>`)
    val, err := polyxml.Deserialize(xml, schema)
    if err != nil {
        log.Fatal(err)
    }

    // Serialize with 2-space indentation
    out, err := polyxml.Serialize("Message", val, schema, 2)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Formatted XML output:\n" + string(out))
}

3. Idiomatic Go Struct Adapter Pattern

In production Go microservices, map polyxml.Value into domain structs for clean separation of concerns:

package main

import (
    "fmt"
    "github.com/polyxml/PolyXML/bindings/go"
)

type TradeConfirmation struct {
    TradeID     int64
    Symbol      string
    Price       float64
    Quantity    int64
    Settled     bool
}

func parseTrade(xml []byte, schema *polyxml.Schema) (*TradeConfirmation, error) {
    val, err := polyxml.Deserialize(xml, schema)
    if err != nil {
        return nil, err
    }

    tradeID, _ := val.GetField("trade_id").GetInt()
    symbol, _ := val.GetField("symbol").GetString()
    price, _ := val.GetField("price").GetFloat()
    qty, _ := val.GetField("quantity").GetInt()
    settled, _ := val.GetField("settled").GetBool()

    return &TradeConfirmation{
        TradeID:  tradeID,
        Symbol:   symbol,
        Price:    price,
        Quantity: qty,
        Settled:  settled,
    }, nil
}

func main() {
    builder, _ := polyxml.NewSchemaBuilder("Trade")
    builder.AddField("trade_id", "id", polyxml.FieldAttribute, polyxml.ScalarInt)
    builder.AddField("symbol", "symbol", polyxml.FieldElement, polyxml.ScalarString)
    builder.AddField("price", "price", polyxml.FieldElement, polyxml.ScalarFloat)
    builder.AddField("quantity", "quantity", polyxml.FieldElement, polyxml.ScalarInt)
    builder.AddField("settled", "settled", polyxml.FieldElement, polyxml.ScalarBool)
    schema, _ := builder.Build()

    xml := []byte(`<Trade id="9821"><symbol>AAPL</symbol><price>224.50</price><quantity>500</quantity><settled>true</settled></Trade>`)
    trade, err := parseTrade(xml, schema)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Executed Trade: %d shares of %s at $%.2f (ID: %d)\n", 
        trade.Quantity, trade.Symbol, trade.Price, trade.TradeID)
}

4. HTTP Service Integration (Replacing encoding/xml)

Use PolyXML in high-throughput HTTP handlers to eliminate garbage collection pressure caused by reflection in standard encoding/xml:

package main

import (
    "io"
    "log"
    "net/http"
    "sync"

    "github.com/polyxml/PolyXML/bindings/go"
)

var (
    payloadSchema *polyxml.Schema
    schemaOnce    sync.Once
)

func getSchema() *polyxml.Schema {
    schemaOnce.Do(func() {
        b, err := polyxml.NewSchemaBuilder("Event")
        if err != nil {
            log.Fatalf("Failed to init schema: %v", err)
        }
        b.AddField("id", "id", polyxml.FieldAttribute, polyxml.ScalarInt)
        b.AddField("type", "type", polyxml.FieldElement, polyxml.ScalarString)
        b.AddField("timestamp", "timestamp", polyxml.FieldElement, polyxml.ScalarString)
        payloadSchema, _ = b.Build()
    })
    return payloadSchema
}

func xmlHandler(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read body", http.StatusBadRequest)
        return
    }

    schema := getSchema()
    val, err := polyxml.Deserialize(body, schema)
    if err != nil {
        http.Error(w, "Invalid XML payload", http.StatusUnprocessableEntity)
        return
    }

    eventType, _ := val.GetField("type").GetString()
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(fmt.Sprintf("Event '%s' processed successfully", eventType)))
}

5. XML Namespaces & Prefix Mapping

PolyXML Go bindings support W3C XML namespaces and custom prefix mappings:

package main

import (
    "fmt"
    "log"

    "github.com/polyxml/PolyXML/bindings/go"
)

func main() {
    builder, err := polyxml.NewSchemaBuilder("Order")
    if err != nil {
        log.Fatalf("Failed to create builder: %v", err)
    }

    // Declare model and field namespaces
    builder.SetNamespace("https://example.com/orders")
    builder.AddField("id", "id", polyxml.FieldAttribute, polyxml.ScalarInt)
    builder.AddFieldWithNamespace("item", "item", polyxml.FieldElement, polyxml.ScalarString, "https://example.com/items")

    schema, _ := builder.Build()

    xml := []byte(`<ns0:Order xmlns:ns0="https://example.com/orders" xmlns:ns1="https://example.com/items" id="505"><ns1:item>GoGadget</ns1:item></ns0:Order>`)
    val, err := polyxml.Deserialize(xml, schema)
    if err != nil {
        log.Fatalf("Deserialization error: %v", err)
    }

    id, _ := val.GetField("id").GetInt()
    item, _ := val.GetField("item").GetString()
    fmt.Printf("Order %d: %s\n", id, item)

    // Serialize with custom prefix mapping
    nsMap := map[string]string{
        "ord": "https://example.com/orders",
        "itm": "https://example.com/items",
    }

    enabled := true
    outBytes, err := polyxml.SerializeWithOptions("Order", val, schema, 2, &enabled, nsMap)
    if err != nil {
        log.Fatalf("Serialization error: %v", err)
    }
    fmt.Println(string(outBytes))
}

6. Memory Management & Garbage Collection

PolyXML Go bindings bind Go's GC to native Rust resources through runtime.SetFinalizer: - When a *polyxml.Schema or *polyxml.Value object becomes unreachable in Go, the Go garbage collector invokes the finalizer which calls C.polyxml_schema_free or C.polyxml_value_free. - Best Practice: In high-throughput batch loops, avoid creating new Schema objects repeatedly. Construct global or long-lived schemas to eliminate allocation cycles.


7. Dual-Format Go Models (XML + JSON Code Generation)

When generating Go code using the PolyXML CLI (polyxml generate --lang go), the generator emits structs equipped with both xml:"..." and json:"..." struct tags:

package banking

import (
    "encoding/json"
    "encoding/xml"
)

// Customer is generated from customer.xsd with dual serialization tags:
type Customer struct {
    XMLName xml.Name `xml:"Customer" json:"-"`
    ID      int      `xml:"id,attr" json:"id"`
    Name    string   `xml:"name" json:"name"`
    Email   *string  `xml:"email,omitempty" json:"email,omitempty"`
}

Zero-Translation XML ↔ JSON Pipeline

Because XMLName is tagged with json:"-" and all fields have corresponding JSON keys, you can ingest XML and immediately emit JSON in microservices without any intermediate mapping structs:

func handleCustomer(xmlData []byte) ([]byte, error) {
    var customer banking.Customer
    if err := xml.Unmarshal(xmlData, &customer); err != nil {
        return nil, err
    }

    // Direct-to-JSON serialization using Go's standard library:
    return json.MarshalIndent(customer, "", "  ")
}

---

## 8. JSON backend options (`--backend easyjson | sonic`)

PolyXML can emit annotations for optional Go JSON tooling:

```bash
# 1. Standard encoding/json (default)
polyxml generate --lang go --backend standard --out ./src/models schema.xsd

# 2. EasyJSON static marshallers
polyxml generate --lang go --backend easyjson --out ./src/models schema.xsd

# 3. Additional Sonic struct tags (see limitation below)
polyxml generate --lang go --backend sonic --out ./src/models schema.xsd

EasyJSON (--backend easyjson)

Emits //easyjson:json comments above struct definitions. Run the EasyJSON CLI tool (easyjson -all models.go) to generate fast, static marshallers and unmarshallers without reflection:

//easyjson:json
type Customer struct {
    XMLName xml.Name `json:"-"`
    ID      int32    `xml:"id,attr" json:"id"`
    Name    string   `xml:"name" json:"name"`
}

ByteDance Sonic (--backend sonic)

Emits sonic:"..." struct tags alongside standard json:"..." tags:

type Customer struct {
    XMLName xml.Name `json:"-" sonic:"-"`
    ID      int32    `xml:"id,attr" json:"id" sonic:"id"`
    Name    string   `xml:"name" json:"name" sonic:"name"`
}

```

Current Sonic releases use the standard json tags for field names; PolyXML’s extra sonic tags do not enable a separate fast path. Use the default Go backend unless you need these tags for another tool.