You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将动态JSON映射至对象?未知属性场景的通用解决方案

Great question! When dealing with dynamic JSON transformations where the output structure can't be predefined (since mappings can completely reshape the input), relying solely on dictionaries can get messy quickly—here's a structured, reusable approach using Swift's protocols, generics, and modular rule-based logic.

通用动态JSON映射框架方案

核心思路

Instead of tying yourself to fixed Codable structs, we can build a system that separates what needs to be transformed (the input JSON) from how it's transformed (the mapping rules). This abstraction makes the solution flexible enough to handle any input/mapping combination without hardcoding structures.

1. Define a Mapping Rule Protocol

First, create a protocol that represents a single, self-contained mapping operation. Each rule knows exactly how to extract data from the input and produce a value for the output.

protocol JSONMappingRule {
    /// The key in the final output JSON this rule will populate
    var outputKey: String { get }
    
    /// Takes raw input data and returns the transformed value for the output key
    func transform(input: [String: Any]) -> Any?
}

This protocol lets you create specialized rules for common transformation patterns:

Example Rule Implementations

  • Key Renaming + Value Modification:
struct RenameAndTransformRule: JSONMappingRule {
    let inputKey: String
    let outputKey: String
    let valueTransformer: (Any) -> Any?
    
    func transform(input: [String: Any]) -> Any? {
        guard let inputValue = input[inputKey] else { return nil }
        return valueTransformer(inputValue)
    }
}

// Usage: Turn "country_code" into "country" with a capitalized value
let countryMappingRule = RenameAndTransformRule(
    inputKey: "country_code",
    outputKey: "country",
    valueTransformer: { ($0 as? String)?.capitalized ?? $0 }
)
  • Multi-Attribute Combination:
struct CombineAttributesRule: JSONMappingRule {
    let inputKeys: [String]
    let outputKey: String
    let combiner: ([Any]) -> Any?
    
    func transform(input: [String: Any]) -> Any? {
        let values = inputKeys.compactMap { input[$0] }
        // Only proceed if all required input attributes exist
        guard values.count == inputKeys.count else { return nil }
        return combiner(values)
    }
}

// Usage: Merge "name" and "age" into a single "nameAge" field
let nameAgeMappingRule = CombineAttributesRule(
    inputKeys: ["name", "age"],
    outputKey: "nameAge",
    combiner: { "\($0[0]) (\($0[1]))" }
)

2. Build a Reusable Mapper Service

Create a lightweight mapper that takes an input dictionary and a list of rules, then applies each rule to generate the final output.

class JSONMapper {
    static func map(input: [String: Any], using rules: [JSONMappingRule]) -> [String: Any] {
        var output = [String: Any]()
        
        for rule in rules {
            if let transformedValue = rule.transform(input: input) {
                output[rule.outputKey] = transformedValue
            }
        }
        
        return output
    }
}

Quick Usage Example

// Sample input JSON (converted to a dictionary)
let personInput: [String: Any] = [
    "name": "Alice",
    "age": 30,
    "country_code": "us"
]

// Assemble your mapping rules
let mappingRules: [JSONMappingRule] = [countryMappingRule, nameAgeMappingRule]

// Generate the transformed output
let mappedPerson = JSONMapper.map(input: personInput, using: mappingRules)
// Result: ["country": "Us", "nameAge": "Alice (30)"]

3. Extend for Nested JSON Structures

If you need to transform nested JSON objects, you can add a rule type that handles recursive mapping:

struct NestedMappingRule: JSONMappingRule {
    let inputKey: String
    let outputKey: String
    let nestedRules: [JSONMappingRule]
    
    func transform(input: [String: Any]) -> Any? {
        guard let nestedInput = input[inputKey] as? [String: Any] else { return nil }
        return JSONMapper.map(input: nestedInput, using: nestedRules)
    }
}

This lets you handle hierarchical data without hardcoding nested structs—just define rules for the child objects and nest them into the parent rule set.

4. Optional: Add Type-Safe Sugar (For Readability)

While the core system uses [String: Any] for maximum flexibility, you can add extension methods to make rule creation more readable and type-safe for common cases:

extension JSONMappingRule where Self == RenameAndTransformRule {
    static func rename(_ inputKey: String, to outputKey: String, transformString: @escaping (String) -> String) -> Self {
        RenameAndTransformRule(
            inputKey: inputKey,
            outputKey: outputKey,
            valueTransformer: { ($0 as? String).map(transformString) ?? $0 }
        )
    }
}

// Now creating rules is cleaner:
let countryRule = JSONMappingRule.rename("country_code", to: "country") { $0.capitalized }

Why This Beats Raw Dictionaries

  • Reusability: Rules can be shared across different input types (e.g., a country code transformation works for Person, Company, or Address JSONs)
  • Maintainability: Each rule handles one specific task, so debugging or modifying transformations is isolated and straightforward
  • Scalability: Adding new transformation types (like default values, filtering, or array mapping) just requires a new JSONMappingRule implementation

内容的提问来源于stack exchange,提问作者avregi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:23:39