Encode a [String: Encodable] dictionary into JSON using JSONEncoder in Swift 4 - dictionary

I am just curious how can I encode a dictionary with String key and Encodable value into JSON.
For example:
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
The keys in this dict are all of type String, but the type of the values vary.
However, all of these types are allowed in JSON.
I am wondering if there is a way to use JSONEncoder in Swift 4 to encode this dict into JSON Data.
I do understand there are other ways without using JSONEncoder to achieve this, but I am just wondering if JSONEncoder is capable of managing this.
The Dictionary do have a func encode(to encoder: Encoder) throws in an extension, but that only applies for constraint Key: Encodable, Key: Hashable, Value: Encodable, whereas for our dict, it needs constraint Key: Encodable, Key: Hashable, Value == Encodable.
Having a struct for this will be sufficient to use JSONEncoder,
struct Test: Encodable {
let int = 1
let double = 3.14
let bool = false
let string = "test"
}
However, I am interested to know if the it can be done without specifying the concrete type but just the Encodable protocol.

Just figured out a way to achieve this with a wrapper:
struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
try self.wrapped.encode(to: encoder)
}
}
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
let wrappedDict = dict.mapValues(EncodableWrapper.init(wrapped:))
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(wrappedDict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)
And here is the result:
{
"Double" : 3.1400000000000001,
"String" : "test",
"Bool" : false,
"Int" : 1
}
I am still not happy with it. If there are any other approaches, I am more than happy to see it.
Thanks!
Edit 1 Moving the wrapper into an extension of JSONEncoder:
extension JSONEncoder {
private struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
try self.wrapped.encode(to: encoder)
}
}
func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
return try self.encode(wrappedDict)
}
}
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(dict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)
Result:
{
"Int" : 1,
"Double" : 3.1400000000000001,
"Bool" : false,
"String" : "test"
}
Edit 2: Take customized strategies into account as per #Hamish 's comments
private extension Encodable {
func encode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
extension JSONEncoder {
private struct EncodableWrapper: Encodable {
let wrapped: Encodable
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try self.wrapped.encode(to: &container)
}
}
func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
return try self.encode(wrappedDict)
}
}

You would need a wrapper since with Encodable protocol to know which item is which to be able to encode it easier.
I suggest Use an enum named JSONValue which has 5 to 6 cases for all Int, String, Double, Array, Dictionary cases. then you can write JSONs in a type-safe way.
This link will help too.
This is how I use it:
indirect enum JSONValue {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case object([String: JSONValue])
case array([JSONValue])
case encoded(Encodable)
}
And then make JSONValue: Encodable and write encoding code for each case.

Related

I don't understand incompatible type error in assignment

Can someone explain why I have the flow error
object type (This type is incompatible with object type Indexable signature is incompatible:)
for the assignment in the last line of
const plain: { [key: string]: string } = { prop: '' };
type TestType = { [key: string]: string | number };
const testVar: TestType = plain;
I have no error if I remove the type specification for plain...
Many thanks !
Vladimir Kurchatkin answered to this question in the issue I opened on github (https://github.com/facebook/flow/issues/5458) :
Objects are invariant by default. You can make it covariant like this:
const plain: { [key: string]: string } = { prop: '' };
type TestType = { +[key: string]: string | number };
const testVar: TestType = plain;

How to flowtype cover this code in a function with dereferenced object fields

I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)

How to check if Dictionary is initialized in swift 3

Given a struct with one property - an empty Dictionary of type <String, Any>:
struct Foo {
var bar:Dictionary<String, Any> = [:]
}
and struct extension with custom initalizer:
extension Foo {
init(json: [String: Any]) {
if let bar = json["bar"] {
self.bar = bar as! Dictionary<String, Any>
}
}
}
I would like to check further down in my code (after instantiating that Struct) if bar property on my Foo struct does not have a default [:] value:
let param:Dictionary<String, Any> = ["bar": 12]
let options = Foo(param)
if (options.bar != [:]) {
// ok, the bar is set - do something
}
the param dictionary is a user-supplied JSON object which must have a type <String, Any> as it can contain eiter String:Int or String:String values.
Unfortunately the above if statement gives me:
Binary operator '!=' cannot be applied to operands of type 'Dictionary<String, Any>' and '[AnyHashable : Any]'
Use isEmpty method
You are intializing wrong here
if let bar = json["bar"] {
self.bar = bar as! Dictionary<String, Any>
}
json["bar"] returns int and casting to Dictionary type,crashes.
Change your extension as
extension Foo {
init(json: [String: Any]) {
if let _ = json["bar"] {
self.bar = json
}
}
}
And then you can check as
let param:Dictionary<String, Any> = ["bar": 12]
let options = Foo(json: param)
if (options.bar.isEmpty) {
print("empty")
}else{
print("not emtpy")
}

flow function declaration syntax

I am trying to document a factory function in my declaration file.
My goal is to make flow aware of my simple factory.
It's used in koa v2 routes and it's a way to inject some options in my service.
Here is the factory:
ctx.compose = function Compose<T: *>(service: Class<T>, options: ?Object): T {
return new service(_.extend({}, ctx._requestOptions, options));
};
Because I use koa v2 I created a type KoaCtx in a declaration that look like this:
declare type KoaCtx = {
params: { [key: string]: string },
request: {
query: { [key: string]: string },
body: { [key: string]: string | boolean | Array<any> | Number | Date | Object },
},
body: any,
req: any,
res: any,
state: any,
...
compose: compose: function <T: *>(service: Class<T>, options: ?Object): T
}
I tried different syntaxes but I keep getting errors.
compose: function <T: *>(service: Class<T>, options: ?Object): T
^ Unexpected token :
If I put the first snippet of code inside my koa route it's working fine!
I tried to add the file with the ctx.compose definition in [include] tag in flow config but it's not working.
Update
Tried with this declaration:
declare function Compose<T: *>(service: Class<T>, options: ?Object): T;
declare type KoaCtx = {
...
compose: Compose<Class<*>>
};
But unfortunately it's still not working.
A function type looks like
(argName1: Type1, argName2: Type2, ...restName: Array<RestType>) => ReturnType
a function type with generics looks like
<T>(argName1: Type1, argName2: Type2, ...restName: Array<RestType>) => ReturnType
So you probably should write
declare type KoaCtx = {
...
compose: <T: *>(service: Class<T>, options: ?Object) => T
}
That said, you're using the existential type * as an upper bound for T. I'm not sure if that makes much sense here. So I'd just recommend writing
declare type KoaCtx = {
...
compose: <T>(service: Class<T>, options: ?Object) => T
}
As for your update, you can only explicitly instantiate type parameters for type aliases, interfaces, and classes. So you could write
declare type Compose = <T>(service: Class<T>, options: ?Object) => T;
declare type KoaCtx = {
...
compose: Compose<MyClass>
};
but you can't do that if Compose is a generic function.

How to access deeply nested dictionaries in Swift

I have a pretty complex data structure in my app, which I need to manipulate. I am trying to keep track of how many types of bugs a player has in thier garden. There are ten types of bugs, each with ten patterns, each pattern having ten colors. So there are 1000 unique bugs possible, and I want to track how many of each of these types the player has. The nested dictionary looks like:
var colorsDict: [String : Int]
var patternsDict: [String : Any] // [String : colorsDict]
var bugsDict: [String : Any] // [String : patternsDict]
I do not get any errors or complaints with this syntax.
When I want to increment the player's bug collection though, doing this:
bugs["ladybug"]["spotted"]["red"]++
I get this error: String is not convertible to 'DictionaryIndex< String, Any >' with the error's carrot under the first string.
Another similar post suggested using "as Any?" in the code, but the OP of that post only had a dictionary one deep so could do that easily with: dict["string"] as Any? ...
I am not sure how to do this with a multilevel dictionary. Any help would be appreciated.
When working with dictionaries you have to remember that a key might not exist in the dictionary. For this reason, dictionaries always return optionals. So each time you access the dictionary by key you have to unwrap at each level as follows:
bugsDict["ladybug"]!["spotted"]!["red"]!++
I presume you know about optionals, but just to be clear, use the exclamation mark if you are 100% sure the key exists in the dictionary, otherwise it's better to use the question mark:
bugsDict["ladybug"]?["spotted"]?["red"]?++
Addendum: This is the code I used for testing in playground:
var colorsDict = [String : Int]()
var patternsDict = [String : [String : Int]] ()
var bugsDict = [String : [String : [String : Int]]] ()
colorsDict["red"] = 1
patternsDict["spotted"] = colorsDict
bugsDict["ladybug"] = patternsDict
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 1
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 2
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 3
bugsDict["ladybug"]!["spotted"]!["red"]! // Prints 4
Another option: You could try calling dict.value( forKeyPath: "ladybug.spotted.red" )!
So I just tried this with Swift 5:
import Foundation
var d = [ "ladybug" : [ "spotted" : [ "red" : 123 ] ] ] as [String:Any]
(d as NSDictionary).value(forKeyPath: "ladybug.spotted.red")
and it works, but this is probably the best way:
d["ladybug"]?["spotted"]?["red"]
I had the same issue, where I wanted to get boolValue nested in dictionary.
{
"Level1": {
"leve2": {
"code": 0,
"boolValue": 1
}
}
}
I tried a lot of solution but those didn't worked for me as i was missing type casting. So I used following code to get the boolValue from json, where json is a nested dictionary of type [String:Any].
let boolValue = ((json["level1"]
as? [String: Any])?["level2"]
as? [String: Any])?["boolValue"] as? Bool
My primary use case was reading ad-hoc values from a deep dictionary. None of the answers given worked for me in my Swift 3.1 project, so I went looking and found Ole Begemann's excellent extension for Swift dictionaries, with a detailed explanation on how it works.
I've made a Github gist with the Swift file I made for using it, and I welcome feedback.
To use it, you can add the Keypath.swift into your project, and then you can simply use a keyPath subscript syntax on any [String:Any] dictionary as follows.
Considering you have a JSON object like so:
{
"name":"John",
"age":30,
"cars": {
"car1":"Ford",
"car2":"BMW",
"car3":"Fiat"
}
}
stored in a dictionary var dict:[String:Any]. You could use the following syntax to get to the various depths of the object.
if let name = data[keyPath:"name"] as? String{
// name has "John"
}
if let age = data[keyPath:"age"] as? Int{
// age has 30
}
if let car1 = data[keyPath:"cars.car1"] as? String{
// car1 has "Ford"
}
Note that the extension supports writing into nested dictionaries as well, but I haven't yet used this.
I still haven't found a way to access arrays within dictionary objects using this, but it's a start! I'm looking for a JSON Pointer implementation for Swift but haven't found one, yet.
If it's only about retrieval (not manipulation) then here's a Dictionary extension for Swift 3 (code ready for pasting into Xcode playground) :
//extension
extension Dictionary where Key: Hashable, Value: Any {
func getValue(forKeyPath components : Array<Any>) -> Any? {
var comps = components;
let key = comps.remove(at: 0)
if let k = key as? Key {
if(comps.count == 0) {
return self[k]
}
if let v = self[k] as? Dictionary<AnyHashable,Any> {
return v.getValue(forKeyPath : comps)
}
}
return nil
}
}
//read json
let json = "{\"a\":{\"b\":\"bla\"},\"val\":10}" //
if let parsed = try JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<AnyHashable,Any>
{
parsed.getValue(forKeyPath: ["a","b"]) //-> "bla"
parsed.getValue(forKeyPath: ["val"]) //-> 10
}
//dictionary with different key types
let test : Dictionary<AnyHashable,Any> = ["a" : ["b" : ["c" : "bla"]], 0 : [ 1 : [ 2 : "bla"]], "four" : [ 5 : "bla"]]
test.getValue(forKeyPath: ["a","b","c"]) //-> "bla"
test.getValue(forKeyPath: ["a","b"]) //-> ["c": "bla"]
test.getValue(forKeyPath: [0,1,2]) //-> "bla"
test.getValue(forKeyPath: ["four",5]) //-> "bla"
test.getValue(forKeyPath: ["a","b","d"]) //-> nil
//dictionary with strings as keys
let test2 = ["one" : [ "two" : "three"]]
test2.getValue(forKeyPath: ["one","two"]) //-> "three"
Unfortunately none of these methods worked for me, so I built my own to use a simple string path like "element0.element1.element256.element1", etc. Hope this save a time for others. (just use a dots between name of elements in string)
Json example:
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
Step 1, convert json String to Dictionary
static func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
Step 2, helper to get a nested objects
//path example: "control.ui.scale"
static func getDictValue(dict:[String: Any], path:String)->Any?{
let arr = path.components(separatedBy: ".")
if(arr.count == 1){
return dict[String(arr[0])]
}
else if (arr.count > 1){
let p = arr[1...arr.count-1].joined(separator: ".")
let d = dict[String(arr[0])] as? [String: Any]
if (d != nil){
return getDictValue(dict:d!, path:p)
}
}
return nil
}
Step 3, use helper
let controlScale = getDictValue(dict:dict, path: "control.ui.scale") as! Double?
print(controlScale)
let controlName = getDictValue(dict:dict, path: "control.name") as! String?
print(controlName)
Returns
0.5
Save
The Swift 4 default: subscript for Dictionaries makes makes updating values in nested Dictionaries much more concise.
Get and Set a default value rather than dealing with optionals:
var dict = [String : [String : String]]()
dict["deep", default: [:]]["nested"] = "dictionary"
print(dict)
// ["deep": ["nested": "dictionary"]]
https://swift.org/blog/dictionary-and-set-improvements/
You can use this extension:
extension Dictionary {
/// - Description
/// - The function will return a value on given keypath
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameter keyPath: keys joined using '.' such as "key1.key2.key3"
func valueForKeyPath <T> (_ keyPath: String) -> T? {
let array = keyPath.components(separatedBy: ".")
return value(array, self) as? T
}
/// - Description:"
/// - The function will return a value on given keypath. It keep calling recursively until reach to the keypath. Here are few sample:
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameters:
/// - keys: array of keys in a keypath
/// - dictionary: The dictionary in which value need to find
private func value(_ keys: [String], _ dictionary: Any?) -> Any? {
guard let dictionary = dictionary as? [String: Any], !keys.isEmpty else {
return nil
}
if keys.count == 1 {
return dictionary[keys[0]]
}
return value(Array(keys.suffix(keys.count - 1)), dictionary[keys[0]])
}
}
Usage:
let dictionary = ["values" : ["intValue": 3]]
let value: Int = dictionary.valueForKeyPath("values.intValue")
You can use the following syntax on Swift 3/4:
if let name = data["name"] as? String {
// name has "John"
}
if let age = data["age"] as? Int {
// age has 30
}
if let car = data["cars"] as? [String:AnyObject],
let car1 = car["car1"] as? String {
// car1 has "Ford"
}
Yet another approach using various overloaded Dictionary subscript implementations:
let dict = makeDictionary(fromJSONString:
"""
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
""")!
dict[Int.self, ["control", "ui", "padding", "top"]] // 1
dict[Int.self, "control", "ui", "padding", "top"] // 2
dict[Int.self, "control.ui.padding.top"] // 3
And the actual implementations:
extension Dictionary {
// 1
subscript<T>(_ type: T.Type, _ pathKeys: [Key]) -> T? {
precondition(pathKeys.count > 0)
if pathKeys.count == 1 {
return self[pathKeys[0]] as? T
}
// Drill down to the innermost dictionary accessible through next-to-last key
var dict: [Key: Value]? = self
for currentKey in pathKeys.dropLast() {
dict = dict?[currentKey] as? [Key: Value]
if dict == nil {
return nil
}
}
return dict?[pathKeys.last!] as? T
}
// 2. Calls 1
subscript<T>(_ type: T.Type, _ pathKeys: Key...) -> T? {
return self[type, pathKeys]
}
}
extension Dictionary where Key == String {
// 3. Calls 1
subscript<T>(_ type: T.Type, _ keyPath: String) -> T? {
return self[type, keyPath.components(separatedBy: ".")]
}
}
func makeDictionary(fromJSONString jsonString: String) -> [String: Any]? {
guard let data = jsonString.data(using: .utf8)
else { return nil}
let ret = try? JSONSerialization.jsonObject(with: data, options: [])
return ret as? [String: Any]
}
Yet another Dictionary extension
public extension Dictionary where Key: Hashable, Value: Any {
subscript(keyPath path: String) -> Value? {
self[keyPath: path.components(separatedBy: ".").compactMap { $0 as? Key }]
}
private subscript(keyPath keys: [Key]) -> Value? {
var keys = keys
switch keys.first {
case .some(let key) where keys.count == 1:
return self[key]
case .some(let key) where keys.count > 1:
keys.removeFirst()
return (self[key] as? Dictionary<Key, Value>)?[keyPath: keys]
default:
return nil
}
}
}
Test code:
let dict: [String: Any] = [
"user": [
"name": "Giorgio",
"surname": "Baldazzi"
]
]
let keyPath = "user.name"
print(String(describing: dict[keyPath: keyPath]))

Resources