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
%24instead 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
$skipvalues - 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¶
- Simpler Feature Set: Less complexity, faster learning
- Mature Documentation: Well-established best practices
- Stable APIs: No deprecation concerns
- Wide Support: Client libraries readily available
OData 4.0+ Knowledge Requirements¶
- Advanced Concepts: Lambda expressions, computed fields
- Modern Tooling: Newer client library versions
- Feature Detection: Capability negotiation patterns
- 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¶
Efficient Text Search¶
# 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¶
- Master OData 3.0 Patterns: Focus on current implementation capabilities
- Use Proper URL Encoding: Always use
%24instead of$ - Implement Smart Pagination: Leverage efficient
$skipperformance - Plan Text Search Strategy: Use
substringof()effectively - 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