Skip to content

OData Version Comparison Guide

The Danish Parliament API implements OData 3.0, which significantly impacts development patterns, client library compatibility, and feature availability. This comprehensive guide explains the implications of OData version differences for developers working with parliamentary data.

OData Evolution Timeline

OData 1.0 (2009)

  • Initial Release: Microsoft's first open data protocol
  • Basic Features: Simple CRUD operations, basic querying
  • Legacy Status: No longer recommended for new implementations

OData 2.0 (2010-2012)

  • Enhanced Querying: Improved $filter and $select operations
  • Metadata Support: Comprehensive schema definitions
  • JSON Support: Added JSON alongside XML formats
  • Batch Operations: Limited batch request support

OData 3.0 (2013-2014)  Current Implementation

  • Mature Standard: Stable, feature-complete protocol
  • Rich Querying: Full filter functions, date operations
  • Performance Focus: Optimized for large datasets
  • Wide Support: Excellent client library ecosystem
  • Production Ready: Battle-tested in enterprise environments

OData 4.0+ (2014-Present)

  • Modern Features: $search, $compute, $apply operations
  • Lambda Expressions: $any and $all operations
  • Enhanced JSON: JSON-first approach with simplified payloads
  • Improved Metadata: More expressive schema definitions
  • Action/Function Support: Custom operations beyond CRUD

Feature Comparison: OData 3.0 vs 4.0+

Core Query Operations

Feature OData 3.0 OData 4.0+ Danish Parliament API
$top  Full Support  Enhanced  Maximum 100 records
$skip  Full Support  Enhanced  No performance penalty
$filter  Rich Functions  Enhanced  Complex expressions
$expand  Navigation  Enhanced  2-3 levels deep
$select  Field Selection  Enhanced  All fields supported
$orderby  Multi-field  Enhanced  Multiple fields
$count L Not Available  Available L Use $inlinecount
$search L Not Available  Available L Use substringof()

Advanced Features

Feature OData 3.0 OData 4.0+ Danish Parliament API
Batch Operations  Limited  Enhanced L Returns HTTP 501
Lambda Expressions L Not Available  $any, $all L Not supported
Computed Fields L Not Available  $compute L Not supported
Aggregation L Not Available  $apply L Not supported
Actions/Functions L Limited  Enhanced L Read-only API

Filter Functions Comparison

OData 3.0 Functions ( Supported)

# String functions
substringof('klima', titel) eq true
startswith(titel, 'Forslag') eq true
endswith(titel, 'lov') eq true

# Date functions
year(opdateringsdato) eq 2025
month(opdateringsdato) eq 9
day(opdateringsdato) eq 10

# Comparison operators
offentlighedskode eq 'O'
id gt 500000
opdateringsdato ge datetime'2025-01-01T00:00:00'

OData 4.0+ Functions (L Not Supported)

# These cause HTTP 400 errors:
$search="climate legislation"
$filter=Tags/any(t: t eq 'environment')
$filter=Categories/all(c: c ne 'draft')
$compute=year(opdateringsdato) as UpdateYear
$apply=groupby((category), aggregate(count as TotalCount))

Supported vs Unsupported Features

 Fully Supported OData 3.0 Features

Core Query Parameters

# Pagination with excellent performance
curl "https://oda.ft.dk/api/Sag?%24skip=10000&%24top=100"

# Complex filtering with date functions
curl "https://oda.ft.dk/api/Sag?%24filter=year(opdateringsdato)%20eq%202025%20and%20offentlighedskode%20eq%20'O'"

# Multi-level relationship expansion
curl "https://oda.ft.dk/api/Afstemning?%24expand=Stemme/Akt%C3%B8r&%24top=5"

# Field selection for bandwidth optimization
curl "https://oda.ft.dk/api/Sag?%24select=id,titel,offentlighedskode&%24top=20"

# Multi-field sorting
curl "https://oda.ft.dk/api/Sag?%24orderby=opdateringsdato%20desc,titel%20asc&%24top=10"

Advanced Filter Expressions

# Nested logical operations
curl "https://oda.ft.dk/api/Sag?%24filter=(offentlighedskode%20eq%20'O'%20and%20year(opdateringsdato)%20eq%202025)%20or%20substringof('klima',titel)"

# Date range queries
curl "https://oda.ft.dk/api/Sag?%24filter=opdateringsdato%20ge%20datetime'2025-01-01T00:00:00'%20and%20opdateringsdato%20le%20datetime'2025-12-31T23:59:59'"

L Unsupported OData 4.0+ Features

Modern Search Operations

# These return HTTP 400 Bad Request:
curl "https://oda.ft.dk/api/Sag?%24search=climate"
curl "https://oda.ft.dk/api/Sag?%24filter=Tags/%24any(t:%20t%20eq%20'environment')"

# Use OData 3.0 alternatives instead:
curl "https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)"

Batch and Aggregation Operations

# Batch operations return HTTP 501:
curl -X POST "https://oda.ft.dk/api/%24batch" 

# Aggregation operations cause connection errors:
curl "https://oda.ft.dk/api/Sag?%24apply=groupby((sagskategori/kategori))"

Client Library Compatibility

Excellent OData 3.0 Support

JavaScript/Node.js

// @odata/client v1.x (OData 3.0 focused)
const { ODataApi } = require('@odata/client');

const api = new ODataApi({
  baseUrl: 'https://oda.ft.dk/api',
  version: '3.0'
});

// Full feature support
const cases = await api
  .entitySet('Sag')
  .filter("offentlighedskode eq 'O'")
  .expand('Sagskategori')
  .top(50)
  .execute();

Python

# requests-odata v0.x (OData 3.0 compatible)
import requests_odata

service = requests_odata.Service(
    'https://oda.ft.dk/api',
    version='3.0'
)

# Native OData 3.0 query building
query = (service
    .entity_set('Sag')
    .filter("year(opdateringsdato) eq 2025")
    .expand('Sagskategori')
    .top(100))

cases = query.get().json()['value']

C# (.NET)

// Microsoft.Data.OData v5.x (OData 3.0)
using Microsoft.Data.OData;

var context = new DataServiceContext(
    new Uri("https://oda.ft.dk/api/"));

var query = context.CreateQuery<Sag>("Sag")
    .Where(s => s.offentlighedskode == "O")
    .Take(100);

var results = query.Execute();

Limited OData 4.0+ Library Support

Modern Libraries Require Adaptation

// @odata/client v2.x+ (OData 4.0 focused)
// Requires configuration for OData 3.0 compatibility
const api = new ODataApi({
  baseUrl: 'https://oda.ft.dk/api',
  version: '3.0', // Must explicitly specify
  metadata: false, // Disable 4.0-specific metadata parsing
  maxPageSize: 100 // Respect API limits
});

Migration Considerations

From OData 2.0 to Current Implementation

Benefits of Upgrading

# Enhanced date operations now available
# OLD: Limited date filtering
curl "https://oda.ft.dk/api/Sag?%24filter=opdateringsdato%20gt%20datetime'2025-01-01'"

# NEW: Rich date functions
curl "https://oda.ft.dk/api/Sag?%24filter=year(opdateringsdato)%20eq%202025%20and%20month(opdateringsdato)%20ge%209"

Breaking Changes

  • URL Encoding: Stricter requirements for %24 instead of $
  • Error Handling: More consistent HTTP status codes
  • Metadata Format: Enhanced schema definitions

Planning for Future OData 4.0+ Migration

Current Workarounds to Document

# Document these patterns for future migration:

# Instead of $search
curl "https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)"

# Instead of $count
curl "https://oda.ft.dk/api/Sag?%24inlinecount=allpages&%24top=1"

# Instead of lambda expressions
curl "https://oda.ft.dk/api/SagAkt%C3%B8r?%24expand=Akt%C3%B8r&%24filter=rolleid%20eq%203"

Performance Implications

OData 3.0 Performance Characteristics

Query Execution Times

# Measured performance (OData 3.0 implementation):
curl "https://oda.ft.dk/api/Sag?%24top=5"                    # ~85ms
curl "https://oda.ft.dk/api/Sag?%24top=100"                  # ~90ms
curl "https://oda.ft.dk/api/Sag?%24skip=10000&%24top=100"   # ~90ms (no penalty)
curl "https://oda.ft.dk/api/Afstemning?%24expand=Stemme"     # ~1.8s (complex)

Memory Efficiency

  • Pagination: No memory penalties for large $skip values
  • Expansion: 2-3 levels deep without significant overhead
  • Filtering: Complex expressions process efficiently
  • Selection: Field selection reduces payload size substantially

OData 4.0+ Performance Expectations

Theoretical Improvements

# OData 4.0+ could offer:
# - $count for efficient pagination
# - $search for faster text queries  
# - $apply for server-side aggregation
# - Streaming JSON for large datasets

However, the current OData 3.0 implementation already delivers excellent performance for parliamentary data access patterns.

Cross-Version Compatibility Strategies

1. Version-Aware Client Development

Detect OData Version

import requests

def detect_odata_version(base_url):
    """Detect OData version from service response headers"""
    response = requests.get(f"{base_url}/Sag?%24top=1")

    # Check for OData version headers
    data_service_version = response.headers.get('DataServiceVersion', '')
    odata_version = response.headers.get('OData-Version', '')

    if '3.0' in data_service_version:
        return '3.0'
    elif '4.0' in odata_version:
        return '4.0'
    else:
        return 'unknown'

# Usage
version = detect_odata_version('https://oda.ft.dk/api')
print(f"Detected OData version: {version}")  # Output: 3.0

2. Fallback Query Patterns

Text Search Compatibility

def search_cases(query_text, odata_version='3.0'):
    """Cross-version text search implementation"""
    base_url = "https://oda.ft.dk/api/Sag"

    if odata_version >= '4.0':
        # OData 4.0+ syntax (not supported yet)
        filter_expr = f"$search={query_text}"
    else:
        # OData 3.0 fallback (current implementation)
        filter_expr = f"%24filter=substringof('{query_text}',titel)"

    return f"{base_url}?{filter_expr}&%24top=50"

# Always uses OData 3.0 pattern for current API
url = search_cases('klima')
# Returns: https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)&%24top=50

3. Feature Detection Patterns

Progressive Enhancement

class ParliamentAPI {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
        this.features = this.detectFeatures();
    }

    async detectFeatures() {
        const features = {
            search: false,
            count: false,
            batch: false,
            lambda: false
        };

        try {
            // Test $search support
            await this.request('Sag?$search=test');
            features.search = true;
        } catch (e) {
            // Falls back to substringof()
        }

        try {
            // Test $count support  
            await this.request('Sag?$count=true');
            features.count = true;
        } catch (e) {
            // Falls back to $inlinecount=allpages
        }

        return features;
    }

    buildTextQuery(searchText) {
        if (this.features.search) {
            return `$search=${encodeURIComponent(searchText)}`;
        } else {
            return `%24filter=substringof('${searchText}',titel)`;
        }
    }
}

Developer Impact Analysis

Learning Curve Implications

OData 3.0 Advantages for New Developers

  1. Simpler Feature Set: Less complexity, faster learning
  2. Mature Documentation: Well-established best practices
  3. Stable APIs: No deprecation concerns
  4. Wide Support: Client libraries readily available

OData 4.0+ Knowledge Requirements

  1. Advanced Concepts: Lambda expressions, computed fields
  2. Modern Tooling: Newer client library versions
  3. Feature Detection: Capability negotiation patterns
  4. Migration Planning: Future upgrade considerations

Development Productivity

OData 3.0 Development Patterns

# Straightforward, predictable patterns
def get_recent_cases(days_back=30):
    """Get cases updated in last N days"""
    cutoff_date = (datetime.now() - timedelta(days=days_back)).isoformat()

    url = (f"https://oda.ft.dk/api/Sag?"
           f"%24filter=opdateringsdato%20ge%20datetime'{cutoff_date}'"
           f"&%24orderby=opdateringsdato%20desc"
           f"&%24top=100")

    response = requests.get(url)
    return response.json()['value']

Reduced Cognitive Load

  • Consistent Patterns: All queries follow same structure
  • Predictable Errors: Well-defined error scenarios
  • Limited Options: Fewer ways to accomplish same task
  • Clear Documentation: Established best practices

Best Practices for Version-Specific Development

1. Embrace OData 3.0 Patterns

# Don't try to force OData 4.0 patterns
# L This fails:
curl "https://oda.ft.dk/api/Sag?%24search=climate"

#  Use OData 3.0 efficiently:
curl "https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)%20or%20substringof('milj%C3%B8',titel)"

Pagination Without $count

def paginate_all_cases():
    """Efficiently paginate without $count support"""
    skip = 0
    batch_size = 100
    all_cases = []

    while True:
        url = f"https://oda.ft.dk/api/Sag?%24skip={skip}&%24top={batch_size}"
        response = requests.get(url)
        batch = response.json()['value']

        if not batch:  # No more results
            break

        all_cases.extend(batch)
        skip += batch_size

        # Respect API limits
        if len(batch) < batch_size:
            break  # Last page

    return all_cases

2. Optimize for Current Implementation

Strategic Relationship Loading

# Load related data efficiently
curl "https://oda.ft.dk/api/Sag?%24expand=Sagskategori,Sagstrin&%24select=id,titel,opdateringsdato,Sagskategori/kategori,Sagstrin/titel&%24top=50"

Error-Resilient Query Building

def build_safe_filter(field, operator, value):
    """Build filters with field validation"""
    # Known valid fields for Sag entity
    valid_fields = {
        'id', 'titel', 'titelkort', 'offentlighedskode',
        'opdateringsdato', 'statusid', 'sagskategoriid'
    }

    if field not in valid_fields:
        raise ValueError(f"Unknown field: {field}")

    # Proper URL encoding
    if operator == 'eq' and isinstance(value, str):
        return f"{field}%20eq%20'{value}'"
    elif operator == 'substringof':
        return f"substringof('{value}',{field})"

    return f"{field}%20{operator}%20{value}"

3. Future-Proof Development

Document Version Dependencies

"""
Danish Parliament API Client

OData Version: 3.0 (confirmed as of 2025-09-10)
Key Limitations:
- No $search support (use substringof)
- No $count support (use $inlinecount=allpages)  
- No batch operations (HTTP 501)
- Maximum $top value: 100
- URL encoding required: $ ’ %24

Migration Notes:
- Ready for OData 4.0 $search ’ substringof mapping
- $inlinecount ’ $count migration path prepared
- Batch operation requirements documented
"""

Conclusion

The Danish Parliament API's OData 3.0 implementation provides excellent functionality for parliamentary data access. While it lacks some modern OData 4.0+ features, the current implementation delivers:

Strengths of Current Version Choice

  •  Proven Stability: Battle-tested in production environments
  •  Excellent Performance: 85ms-2s response times across all operations
  •  Rich Querying: Complex filtering, expansion, and pagination
  •  Wide Compatibility: Supported by mature client libraries
  •  Consistent Behavior: Predictable error handling and responses

Development Recommendations

  1. Master OData 3.0 Patterns: Focus on current implementation capabilities
  2. Use Proper URL Encoding: Always use %24 instead of $
  3. Implement Smart Pagination: Leverage efficient $skip performance
  4. Plan Text Search Strategy: Use substringof() effectively
  5. Document Version Dependencies: Prepare for future migrations

The OData 3.0 implementation serves the Danish Parliament's transparency mission excellently, providing developers with reliable, high-performance access to comprehensive parliamentary data spanning 74+ years of democratic processes.

For practical implementation guidance, see: - OData Overview - Current implementation details - Filters - Text search patterns - Performance - Optimization strategies