Opxf 0.3.0

dotnet add package Opxf --version 0.3.0
                    
NuGet\Install-Package Opxf -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Opxf" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Opxf" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Opxf" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Opxf --version 0.3.0
                    
#r "nuget: Opxf, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Opxf@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Opxf&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Opxf&version=0.3.0
                    
Install as a Cake Tool

opxf-dotnet

Strongly-typed .NET library for the Open Product Exchange Format (OPXF) — POCOs, System.Text.Json converters, and cross-file conformance validation.

Overview

OPXF is a PIM-agnostic transit format for moving product data from enterprise PIM systems (Akeneo, Inriver, Struct, Pimcore, Bluestone) into downstream consumers (e-commerce platforms, PDF engines, and other channels). This library provides the .NET foundation for working with OPXF payloads.

An OPXF exchange consists of two files:

File Purpose
model.json The Definition Manifest — attribute types, localization rules, value lists, product types, category trees
data.json The Product Payload — products, variants, supporting objects, and assets

Installation

dotnet add package OPXF

Targets .NET Standard 2.0, .NET 8, and .NET 9 — so it can be consumed from .NET Framework 4.6.1+, .NET Core 2.0+, and modern .NET alike. On .NET Standard 2.0 it pulls in the System.Text.Json package; .NET 8/9 use the in-box version.

Note (next major rework): .NET Standard 2.0 support currently relies on a small set of internal polyfills in src/OPXF/Compatibility/NetStandardPolyfills.cs — compiler-required types (IsExternalInit, RequiredMemberAttribute, CompilerFeatureRequiredAttribute) for the init/required syntax, plus convenience wrappers (ToHashSet, KeyValuePair.Deconstruct). For the next version, rework the project to drop these wrappers and adjust the code so it compiles natively against .NET Standard 2.0 and the other targets without polyfills (e.g. replace xs.ToHashSet() with new HashSet<>(xs) and deconstructing foreach loops with explicit KeyValuePair access; reconsider the init/required POCO shape if the compiler-attribute polyfills are also to be removed).

Usage

Deserialize

using OPXF;

var model = OpxfSerializer.DeserializeModel(File.ReadAllText("model.json"));
var data  = OpxfSerializer.DeserializeData(File.ReadAllText("data.json"));

Validate

using OPXF.Validation;

var validator = new OPXFValidator();
var result = validator.Validate(model, data);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
        Console.WriteLine(error); // [CONF-02] /data/products/0/productTypeId: unknown productTypeId 'apparel'
}

Serialize

string json = OpxfSerializer.SerializeData(data);

Stream-based I/O

For large payloads, use the stream overloads to avoid loading the full JSON into a string:

await using var modelStream = File.OpenRead("model.json");
await using var dataStream  = File.OpenRead("data.json");

var model = await OpxfSerializer.DeserializeModelAsync(modelStream);
var data  = await OpxfSerializer.DeserializeDataAsync(dataStream);

await using var outStream = File.Create("data.out.json");
await OpxfSerializer.SerializeDataAsync(data, outStream);

Work with attribute values

using OPXF.Data;
using OPXF.Model;

foreach (var product in data.Data.Products)
{
    foreach (var (attrId, entry) in product.Attributes ?? [])
    {
        // Value shape depends on the model's localizable flag:
        //   localizable: false → string | double | bool | string[] | JsonElement
        //   localizable: true  → IReadOnlyDictionary<string, object> keyed by BCP 47 locale
        if (entry.Value is IReadOnlyDictionary<string, object> localeMap)
        {
            var enValue = localeMap["en-GB"];
        }
        else
        {
            var value = entry.Value;
        }
    }
}

Namespaces

Namespace Contents
OPXF OpxfSerializer — entry point for serialization; LocalizedTextExtensionsResolve/ResolveOr for locale-keyed labels
OPXF.Model OpxfModelFile, ModelDefinition, AttributeDefinition, AttributeType (enum), ProductType, ObjectType, AssetType, CategoryType, Channel, Market, AttributeGroup, SelectValue; ModelLookupExtensions + ModelIndex — id resolvers
OPXF.Data OpxfDataFile, DataPayload, Product, Variant, OpxfObject, Asset, Lifecycle (enum), TransferMode (enum), AttributeMap, AttributeValueEntry, CategoryGroup, CategoryNode
OPXF.Validation OPXFValidator, ValidationResult, ValidationError

Working with values

Attribute values are stored as object (their shape depends on the model), but you rarely need to cast by hand. AttributeValueEntry exposes typed accessors:

var attrs = product.Attributes!;

double weight = attrs["weight"].AsNumber();          // throws if not a number
bool   isNew  = attrs["is_new"].AsBool();
string fit    = attrs["fit"].AsString();             // select id, link id, text, …
var    tags   = attrs["tags"].AsStringList();        // text-list / select-list / *-link-list
JsonElement raw = attrs["spec_sheet"].AsJson();      // json attributes

if (attrs["sku"].TryAsString(out var sku)) { /* … */ }   // non-throwing variants

// Localizable attributes carry a locale-keyed value:
var name = attrs["name"];
if (name.IsLocalized)
    string? enName = (string?)name.ForLocale("en-GB", fallbackLocale: "en-US");

Locale-keyed labels (Label, Description, etc.) have resolution helpers:

string? colour = attr.Label.Resolve("de-DE", fallbackLocale: model.DefaultLocale);
string  shown  = attr.Label.ResolveOr("de-DE", defaultValue: attr.Id);

And model ids resolve without manual First(...) scans:

var pt  = model.Model.ProductTypeById(product.ProductTypeId);   // one-off lookup
var def = model.Model.ProductAttributeById("colour");

var index = model.Model.Index();                                // O(1) for repeated lookups
foreach (var p in data.Data.Products)
    var t = index.ProductType(p.ProductTypeId);

Conformance validation

OPXFValidator.Validate() enforces the cross-file rules from docs/conformance.md — constraints that JSON Schema alone cannot express. Each ValidationError carries:

  • RuleId — the conformance rule code, e.g. CONF-02
  • Message — human-readable description of the violation
  • Path — RFC 6901 JSON Pointer to the offending element, e.g. /data/products/0/productTypeId

OPXFValidator.ValidateModel() validates model internal integrity (CONF-31 through CONF-43, plus the model-side market rules CONF-55..57) without a data file.

Validators collect all violations in a single pass — a payload returns all errors at once, not just the first.

Transfer mode

opxf.transferMode is required and modeled as the TransferMode enum (Snapshot / Incremental) — a missing or unknown value is a deserialization error, not a silent default. When Incremental, referential existence rules (CONF-28, CONF-29, CONF-38) are relaxed — missing targets are the exporter's responsibility. Snapshot enforces them fully.

Conformance rules implemented

Section 1 — Model/Data binding

Rule Description
CONF-01 opxf.modelId in the data file must equal model.id in the model file

Section 2 — Products

Rule Description
CONF-02 product.productTypeId must reference a defined product type
CONF-03 Every product.categories entry must reference a defined category node
CONF-04 Product attribute ids must be declared in productType.productAttributes
CONF-05 No two products may share the same id

Section 3 — Variants

Rule Description
CONF-06 Variant attribute ids must be declared in productType.variantAttributes; partial coverage allowed
CONF-07 No two variants within the same product may share the same id

Section 4 — Objects

Rule Description
CONF-09 objectTypeId must reference a defined object type
CONF-10 Object attribute ids must be declared in the resolved objectType.attributeDefinitions
CONF-11 No two objects within the same type group may share the same id

Section 5 — Assets

Rule Description
CONF-12 assetTypeId must reference a defined asset type
CONF-13 Asset attribute ids must be declared in the resolved assetType.attributeDefinitions
CONF-14 No two assets within the same type group may share the same id

Section 6 — Attribute value types

Rule Description
CONF-15 Attributes with localizable: true must have value as a locale-keyed object
CONF-16 Attributes with localizable: false must have value as a direct primitive
CONF-17 Every locale key in any locale-keyed object (attribute values and all labels) must be in model.locales
CONF-18 boolean values must be a JSON boolean — not the string "true"
CONF-19 number values must be a JSON number
CONF-20 text, textarea, and datetime values must be a string
CONF-21 text-list values must be an array of strings
CONF-22 select values must match a selectValue id; localizable select allows different ids per locale
CONF-23 select-list values must be an array of valid selectValue ids
CONF-24 object-link values must reference an existing object id in the correct type group
CONF-25 object-link-list values must be an array of existing object ids in the correct type group
CONF-26 asset-link values must reference an existing asset id in the correct type group
CONF-27 asset-link-list values must be an array of existing asset ids in the correct type group
CONF-28 product-link values must reference an existing product or variant id (snapshot mode only)
CONF-29 product-link-list values must be an array of existing product or variant ids (snapshot mode only)
CONF-30 json attributes accept any valid JSON value — no further enforcement

Section 7 — Categories

Rule Description
CONF-44 categoryTreeId values must be unique across all groups in data.categories
CONF-45 A category node with attribute keys must belong to a group that declares a categoryTypeId
CONF-46 Category node ids must be unique across the entire tree at all depths
CONF-47 Category node attribute ids must be declared in the resolved categoryType.attributeDefinitions
CONF-49 categoryTypeId on a category group must reference a defined categoryType
CONF-50 categoryType ids must be unique within model.categoryTypes

Section 8 — Model internal integrity

Rule Description
CONF-31 productType.productAttributes ids must exist in productAttributeDefinitions
CONF-32 productType.variantAttributes ids must exist in productAttributeDefinitions
CONF-33 Attribute definition ids within each objectType must be unique
CONF-34 Attribute definition ids within each assetType must be unique
CONF-35 selectValue ids within each attribute must be unique
CONF-36 attributeDefinition.objectTypeId must reference a defined objectType
CONF-37 attributeDefinition.assetTypeId must reference a defined assetType
CONF-39 channel.locales must be a subset of model.locales
CONF-40 model.defaultLocale must be present in model.locales
CONF-41 ids within each model-root array must be unique within that array
CONF-43 attributeDefinition.groupId must reference a defined attributeGroup in the correct scope

Section 9 — Entity id format

Rule Description
CONF-51 All ids must match ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ (also enforced by JSON Schema)

Section 10 — Cross-file references

Rule Description
CONF-38 channel.categoryTreeIds must reference trees present in data.categories (snapshot mode only)

Section 11 — Lifecycle and tombstones

Rule Description
CONF-52 lifecycle is required on every product, variant, object, and asset, and must be one of draft, active, archived, deleted (also enforced by the schema and the non-nullable POCO property)
CONF-53 A reference to a deleted entity is valid and must not be flagged as a broken reference — the tombstone is carried in the payload so links resolve
CONF-54 A deleted tombstone is the only conformant delete signal; absence of an entity does not imply deletion

Section 12 — Markets and market scoping

Rule Description
CONF-55 market.locales must be a subset of model.locales
CONF-56 market ids must be unique within model.markets
CONF-57 Every activeForMarkets id (on attribute definitions, select values, products, variants, objects, and assets) must reference a defined market — enforced in both snapshot and incremental mode
CONF-58 Market scoping resolves per market then projects to locales; absent or empty activeForMarkets means all markets (narrow-only, no "scoped to nothing" state). Resolution semantics — not a producer-side violation

Lifecycle

Every product, variant, object, and asset carries a mandatory Lifecycle (OPXF 0.2):

Lifecycle.Draft     // "draft"    — not yet published
Lifecycle.Active    // "active"   — live and current
Lifecycle.Archived  // "archived" — retired but retained for reference
Lifecycle.Deleted   // "deleted"  — tombstone: remove downstream (still carried so links resolve)

Assets additionally carry a required UrlUpdatedAt (DateTimeOffset) whenever a Url is present, for cache-busting and change detection.

Attribute types

AttributeType is an enum of all OPXF canonical types. Each member serializes to its kebab-case wire string via the built-in converter:

AttributeType.Text            // "text"
AttributeType.TextList        // "text-list"
AttributeType.Textarea        // "textarea"
AttributeType.Number          // "number"
AttributeType.Boolean         // "boolean"
AttributeType.Datetime        // "datetime"
AttributeType.Select          // "select"
AttributeType.SelectList      // "select-list"
AttributeType.AssetLink       // "asset-link"
AttributeType.AssetLinkList   // "asset-link-list"
AttributeType.ObjectLink      // "object-link"
AttributeType.ObjectLinkList  // "object-link-list"
AttributeType.ProductLink     // "product-link"
AttributeType.ProductLinkList // "product-link-list"
AttributeType.Json            // "json"
Repository Description
opxf/opxf-spec The OPXF specification — JSON schemas and conformance rules
opxf/opxf-dotnet-akeneo Akeneo → OPXF exporter built on this library
opxf/opxf-org Source for opxf.org

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 92 6/23/2026
0.2.1 99 6/16/2026
0.2.0 98 6/12/2026
0.1.0 113 5/29/2026