Objectmapper: Mapping dictionary of dictionary to object

Created on 23 Nov 2016  路  10Comments  路  Source: tristanhimmelman/ObjectMapper

I have not been able to figure out how to map this JSON

{
    "market_estimates": {
        "2000": {
            "0": 12830, 
            "10000": 11041
        },
        "2001": {
            "0": 12830, 
            "10000": 11041
        }
    }
}

Into an object like this:

class MarketEstimate {
    var year: Int = 0
    var price: Int = 0
    var mileage: Int = 0
}

How do I perform this task?

Most helpful comment

I ended up writing a custom mapping method which tries to map array or dictionary to array. In dictionary case key is passed by using Context.

extension Map {
    func valueFromArrayOrDictionary<Element: ImmutableMappable >(_ key: String, nested: Bool? = nil, delimiter: String = ".", file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [Element] {
        let currentValue = self.currentValue(for: key, nested: nested, delimiter: delimiter)

        guard let JSONObject = currentValue else {
            throw MapError(key: key, currentValue: currentValue, reason: "Found unexpected nil value", file: file, function: function, line: line)
        }

        return try Mapper<Element>().mapArrayOrDictionary(JSONObject: JSONObject)
    }

    fileprivate func currentValue(for key: String, nested: Bool? = nil, delimiter: String = ".") -> Any? {
        let isNested = nested ?? key.contains(delimiter)
        return self[key, nested: isNested, delimiter: delimiter].currentValue
    }
}

extension Mapper where N: ImmutableMappable {
    func mapArrayOrDictionary(JSONObject: Any) throws -> [N] {
        if let dictionary = JSONObject as? [String: Any] {
            return try dictionary.map { try N(JSONObject: $0.value, context: FirebaseContext(key: $0.key)) }
        } else if let array = JSONObject as? [Any] {
            return try array.map { try N(JSONObject: $0) }
        } else {
            throw MapError(key: nil, currentValue: JSONObject, reason: "Cannot cast to '[String: Any] or [Any]")
        }
    }
}

All 10 comments

@mikkelam, do you want an array of MarketEstimate?

Yes, I am sorry i did not specify. Do you know how I could solve this?

Is this what you want? I have no idea where price and mileage come from.

Input

{
    "market_estimates": {
        "2000": {
            "0": 12830, 
            "10000": 11041
        },
        "2001": {
            "0": 12830, 
            "10000": 11041
        }
    }
}

Output

[
  MarketEstimate(year: 2000, price: 12830, mileage: 11041),
  MarketEstimate(year: 2001, price: 12830, mileage: 11041),
]

Like this

[
  MarketEstimate(year: 2000, mileage: 0, price: 12830),
  MarketEstimate(year: 2000, mileage: 10000, price: 11041),
   MarketEstimate(year: 2001, mileage: 0, price: 12830),
  MarketEstimate(year: 2001, mileage: 10000, price: 11041),
]

You have to manipulate that json. Try:

let dict: [String: Any] = [
  "market_estimates": [
    "2000": [
      "0": 12830,
      "10000": 11041
    ],
    "2001": [
      "0": 12830,
      "10000": 11041
    ]
  ]
]

// [
//   {
//     "year": 2000,
//     "mileage": 0,
//     "price": 12830
//   },
//   {
//     "year": 2000,
//     "mileage": 10000,
//     "price": 11041
//   },
//   {
//     "year": 2001,
//     "mileage": 0,
//     "price": 12830
//   },
//   {
//     "year": 2001,
//     "mileage": 10000,
//     "price": 11041
//   }
// ]
let jsonArray = (dict["market_estimates"] as? [String: [String: Int]] ?? [:])
  .flatMap { yearString, mileageAndPrice in
    return mileageAndPrice.flatMap { mileageString, price -> [String: Int]? in
      guard let year = Int(yearString), let mileage = Int(mileageString) else { return nil }
      return [
        "year": year,
        "mileage": mileage,
        "price": price,
      ]
    }
  }
let estimates = Mapper<MarketEstimate>().mapArray(JSONArray: jsonArray)
print(estimates)

Do you have any alternative solution which could work for every dictionary of dictionaries? I'm using Firebase where every array is stored as dictionaries like in @mikkelam 's example.

I ended up writing a custom mapping method which tries to map array or dictionary to array. In dictionary case key is passed by using Context.

extension Map {
    func valueFromArrayOrDictionary<Element: ImmutableMappable >(_ key: String, nested: Bool? = nil, delimiter: String = ".", file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [Element] {
        let currentValue = self.currentValue(for: key, nested: nested, delimiter: delimiter)

        guard let JSONObject = currentValue else {
            throw MapError(key: key, currentValue: currentValue, reason: "Found unexpected nil value", file: file, function: function, line: line)
        }

        return try Mapper<Element>().mapArrayOrDictionary(JSONObject: JSONObject)
    }

    fileprivate func currentValue(for key: String, nested: Bool? = nil, delimiter: String = ".") -> Any? {
        let isNested = nested ?? key.contains(delimiter)
        return self[key, nested: isNested, delimiter: delimiter].currentValue
    }
}

extension Mapper where N: ImmutableMappable {
    func mapArrayOrDictionary(JSONObject: Any) throws -> [N] {
        if let dictionary = JSONObject as? [String: Any] {
            return try dictionary.map { try N(JSONObject: $0.value, context: FirebaseContext(key: $0.key)) }
        } else if let array = JSONObject as? [Any] {
            return try array.map { try N(JSONObject: $0) }
        } else {
            throw MapError(key: nil, currentValue: JSONObject, reason: "Cannot cast to '[String: Any] or [Any]")
        }
    }
}

simple way:

class MarketEstimate {
    var price: Int!
    var mileage: Int!
}
class MarketEstimates {
   var market_estimates:Dictionary<String, MarketEstimate >!
}

then you would have get your structure in market_estimates["2000"], but without the year inside the struct...

@altagir The reason I did not use a dictionary is because it is not supported by Realm.

I ended up writing a custom mapping method which tries to map array or dictionary to array. In dictionary case key is passed by using Context.

extension Map {
  func valueFromArrayOrDictionary<Element: ImmutableMappable >(_ key: String, nested: Bool? = nil, delimiter: String = ".", file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [Element] {
      let currentValue = self.currentValue(for: key, nested: nested, delimiter: delimiter)

      guard let JSONObject = currentValue else {
          throw MapError(key: key, currentValue: currentValue, reason: "Found unexpected nil value", file: file, function: function, line: line)
      }

      return try Mapper<Element>().mapArrayOrDictionary(JSONObject: JSONObject)
  }

  fileprivate func currentValue(for key: String, nested: Bool? = nil, delimiter: String = ".") -> Any? {
      let isNested = nested ?? key.contains(delimiter)
      return self[key, nested: isNested, delimiter: delimiter].currentValue
  }
}

extension Mapper where N: ImmutableMappable {
  func mapArrayOrDictionary(JSONObject: Any) throws -> [N] {
      if let dictionary = JSONObject as? [String: Any] {
          return try dictionary.map { try N(JSONObject: $0.value, context: FirebaseContext(key: $0.key)) }
      } else if let array = JSONObject as? [Any] {
          return try array.map { try N(JSONObject: $0) }
      } else {
          throw MapError(key: nil, currentValue: JSONObject, reason: "Cannot cast to '[String: Any] or [Any]")
      }
  }
}

Could you give an example how to use this extension?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

liltimtim picture liltimtim  路  3Comments

maksTheAwesome picture maksTheAwesome  路  4Comments

patchthecode picture patchthecode  路  3Comments

danyalaytekin picture danyalaytekin  路  4Comments

pcompassion picture pcompassion  路  3Comments