How would you solve this Codable issue?

Ok, maybe it's not exactly a Codable issue.

I am working on a websocket server. The server receives a message in Json:

{"auth":{"username":"byname","password":"pass"}}

The server itself reads the message and only cares about the "auth" key. It uses that key to route the message to the appropriate module. In this case the auth module.

The message is then routed to the auth module, who will try to decode the message into a struct.

I am thinking on doing a protocol Message. The protocol will have a variable named "raw" which will be a String. I will implement an extension on message, to read the routing key (auth).

I need to decode the message without parsing to an object yet. Because the server does not know what the module model looks like. So it passes a message which has a raw.

Now the module when it gets the message, it wants to parse it to its own model. In this case: username, password. If I apply Codable at this moment the message will have to have this structure: auth = ["username":"xxx","password":xxx"].. I will like to only have a struct with username and password not with auth nested..

How will you remove the auth key and extract the value without knowing that kind of values it holds? ANY does not seem to work with Codable. With manual JSON parsing does work.. But my approach seems cumbersome.

I tried removing the key by hand by inspecting the String, but it has to account for a lot of edge cases where it might fail by LF, TABS, etc....

My only solution seems to convert from String JSON to a dictionary of [String:Any] and then extracting the Any part and coding it back to JSON, and then decode it again in the actual model struct.

My brain is blowing :blush:. I am a newbie and I hope someone could shine some light on what would be the best approach to do it.

In summary. I want to inspect a JSON string to return the first key. Then I want to remove that key or get the child objects from the key, and pass it to another func which will in turn use Codable to parse the string into the model. My question is what would be the most efficient and clean way in your POV of doing this

Thanks a lot for the help in advance

I fail to see why this wouldn't work.

struct AuthenticationCredential: Codable {
  var username, password: String
}
struct AuthenticableData: Codable {
  var auth: AuthenticationCredential
}

let message: String = """
  {
    "auth": { "username": "byname", "password": "pass" },
    "someUnusedKey": "someUnusedData"
  }
"""

let decoder = JSONDecoder()
let decoded = decoder.decode(AuthenticableData.self, from: Data(message.utf8))

/*
message: String = "  {\n    \"auth\": { \"username\": \"byname\", \"password\": \"pass\" },\n    \"someUnusedKey\": \"someUnusedData\"\n}"
decoder: Foundation.JSONDecoder = {
  dateDecodingStrategy = deferredToDate
  dataDecodingStrategy = base64
  nonConformingFloatDecodingStrategy = throw
  keyDecodingStrategy = useDefaultKeys
  userInfo = 0 key/value pairs
}
decoded: AuthenticableData = {
  auth = {
    username = "byname"
    password = "pass"
  }
}
*/

As any JSON data not used by decoder will be discarded. And you can decode message again anyway.


but it does know what a message look like. Rather, you do know what a message look like. So you can create your own struct in the likeness of the message and use that to decode data.


It seems all the JSON message of interest is in the form

{
  "auth": { "username": "someString", "password": "anotherString" },
  "otherdata": "random stuff"
}

in which case we can always use AuthenticableData.

As for more information on how to encode data, there's a WWDC video here which may be useful.

*thank you so much for the response.

Your code does work. The problem is that the server our router that reads “auth” doesn’t know about the rest of the data structure. And it doesn’t want to know because it will be siloed in modules. The server when it gets instantiateted, the services or modules register to whatever key they respond to. I dont want the server knowing about the payload structure. However I want the server to deliver the contents of the key object. So in this case, the modules will not know about the Auth parent object and the server will not know about the payload. So I need to “remove” the Auth part and only pass the payload.

I will take another look at the video.

Could you provide more concrete message example?

Right now there are 2 entities/subentities (server and modules), and multiple message components (auth, key, payload, content of the key object, auth parent object). And I’m still confused which part of the message is known to which entity, which part is not, and which part does each entity want to preserve, discard, and process.

Perhaps a JSON with some keys and “blah” values.

Sorry for the confusion.. here are more examples:

{"publish":{"eventid":103145,"text":"this is a text"}}

{"ping":{"payload":""}}

{"users":{"add":{"username":"usr","pass":"ps"}}}

Here the "router" or server is just checking the first key: publish, ping, users.. The rest should be extracted into a dictionary of "any" that can be parsed later to "Module" which in turn with pass or cast that dictionary to a concrete type like a struct:

struct user {
  var username: String
  var password: String
}

However, the module that receives the dictionary does not know about the key. And does not care about it. So in the router or server I need to make a sub dictionary without the key or parent and only pass the payload dictionary to the module..

This is to separate the server "routing" implementation from the module building implementation.

I am sorry if I am still not being clear :frowning:

I see, so you message contains routing-related keys, and module related keys, both keys are on the same level.
You want to extract all routing-related keys, remove it, then pass the rest onto the next part of the program.

If the structure on module-related and routing-related keys are fixed. I’d suggest that you include both in a single Codable anyway. Codable is designed so that you want to have one struct for each point of conversion between data, and internal representation, reusing some sub-structure if possible. Something like this

struct FullMessageData {
  // Both routing data & module data
}

struct RoutingData {
  // Routing data only
}

struct ModuleData {
  // Module data only
}

let fullMessage: FullMessageData = ...
let routingData = fullMessage.routingData
let moduleData = fullMessage.moduleData

If the message logic makes it too cumbersome. Another way is to parse the data twice, once as RoutingData, again as ModuleData.
Because you’re not done with parsing when in routing progam, you’ll want to pass the entire message to the module, and have it ignore the routing keys.

struct RoutingData { ... }
struct ModuleData { ... }

let routingData = JSONDecoder.decode(RoutingData.self, ...)
let moduleData = JSONDecoder.decode(ModuleData.self, ...)

If all else fails, you can still use JSONSerialization which IMO isn’t exactly Swifty, but does exactly what you need.

1 Like

Quick Question.. How does ModuleData only gets parsed since its a level down in the tree?

Maybe I need to rethink the protocol and do it differently...

Let me try to understand this again, you have 3 different kinds of JSON, and want to check which kind the JSON is, as well as parsing it, like this?

// Message type 1
{ 
  "publish": {
    "eventid": 103145,
    "text": "this is a text"
  }
}

// Message type 2
{
  "ping": {
    "payload": ""
  }
}

// Message type 3
{
  "users": {
    "add": {
      "username": "usr",
      "pass":"ps"
    }
  }
}

// If it's type 3, convert to RoutingData
// if it's type 2, convert to ModuleData

Or you have a single message, and want to extract each part of the messages, like this?

// Message
{ 
  "publish": {
    "eventid": 103145,
    "text": "this is a text"
  },
  "ping": {
    "payload": ""
  },
  "users": {
    "add": {
      "username": "usr",
      "pass":"ps"
    }
  }
}

// Routing data wants only users field
// Module data wants only payload field

No, it's the first.. 3 different kind of JSONS..

So the server only cares about the first key: Publish, Ping, Users

And with that data, it routes the message to a module... Who in turn only cares only about the contents of the second key.

I Could make a codable struct in say, users:

struct Wrapper: Codable {
   var users: Users
}
struct Users: Codable {
    var username: String
    var password: String
}

And once I get the message from the server, I parse it with codable into wrapper. But seems that the wrapper is an unnecessary step.

My way of thinking is to remove the root key all together and send the dictionary to the module who in turns decodes it into a Users struct. Since the server will not know about the User struct.. Only the module...

I am a little bit burned!

I see, so the RoutingData can be Publish, Ping, or User where

  • It is exactly one of this for any message
  • It can be inferred by JSON string alone

I that case I'd suggest that you have an umbrellal type RoutingData, and try to convert to any of them inside Swift logic

struct RoutingData {
  var publish: Publish?
  var ping: Ping?
  var user: User?

  var concreteValue: Any {
    if publish != nil {
      return publish
    }
    if ping != nil {
      return ping
    }
    return user
  }
}

/// Reading data
let routingData = JSONDecoder.decode(RoutingData.self, ...)
switch routingData.concreteValue {
case let x where x is Publish:
  #warning("Publish logic here")
case let x where x is Ping:
  #warning("Ping logic here")
case let x where x is User:
  #warning("User logic here")
default:
  fatalError("Unsupported message")
}

Another problem you have is that you want to remove parts of the message before sending it to module. The only good way to do that would be to encode it, strip out the unwanted data, then decode it again. This is tricky, and I'd suggest not doing it unless it's necessary.

  • If you have knowledge of ModuleData, you may want to do the entire encoding in one go.
  • If you don't know what ModuleData looks like, I'd suggest that you use JSONSerialization.

Thank you. I will take a look at how to do this. I am starting to give up on the whole idea. I might switch to a flatter structure...

{"module":"user", "action":"add",
"user":{"username":"xxxx","password":"pass"}}

That way I might use a protocol with associatedType...

protocol myProt {
associatedType: M
var module: String
var action: String
var payload: M
}

Feels more swift.. But the protocol doesn't seem natural.

Your idea of an umbrella type doesn't sound too bad.. I will study implications.. But it does break modularity from the server...

Yeah, if you usually do this kind of gymnastic when the message isn’t designed with swift codable in mind.

If you have control over message structure, redesigning it is probably a good idea.

What about a dynamic struct with a custom type. Maybe I can add a method to register types on runtime, and when a Message is received, it can try to parse it... once it finds one that can be parsed, the struct will be loaded and boom... sounds like a good try! I will make some tests on playgrounds..will le you know if it works!

Are you sure you don't have a full list of possible types at compile time? That would be a lot faster.

Yeah.. I am thinking more and more on your idea.. I do have a list.. Just wanted to separate code that requires to know into a module. But I guess it can be done.

I settled for a flatter structure:

{
"type":"auth,
"verb":"login",
"user":{"username":"myuser","pass":"password"}
}

user represents an object. depending on the module.

I think its easier this way and cleaner. I can parse it to a model in the router where it understands "type". And then pass the raw and parse it again in the model in the module where it will ignore "type"....

What do you think?

1 Like