0

The gist of my question is: how can I convince PHP to encode/decode json without converting [NS]arrays or [NS]dictionaries to the other one?


I've got an app (iOS, though it shouldn't matter) that keeps a bunch of app data in an NSDictionary. Some of the data is nested a few levels, and some of the objects stored in my top-level dictionary are NSArray-s, or other NSDictionary-s, some of which contain additional NSArray-s or NSDictionary-s.

It's not really all that complicated, and the code traverses it all just fine, so that's all well & good.

I save this top-level dict to a server and, to do so, I use NSJSONSerialization, zip the resulting string and upload it. Later, the server sends back the zip file, I use NSJSONSerialization to decode, and we're all back in business -- again, all well & good.

The problem arrises in that, under certain circumstances, I want the server to unzip the file, json-decode the contents, alter the contents, json-encode the result, and re-zip before sending the data back to the app.

"So what's the problem?", I hear you ask. Aha!

The problem is: the server is PHP and uses json_encode() and json_decode() and, if my data contains empty dictionaries, they are converted to arrays, which makes my data-parsing code unhappy.

Further, if I use json_encode($foo, JSON_FORCE_OBJECT), that turns all arrays into dictionaries (keyed by index, if they didn't used to be dictionaries), which is every bit as bad.

So my question is:

Is there any way in PHP to encode/decode json such that what began life as an array remains an array (in the NSArray sense) and what began life as a dictionary (NSDictionary) remains a dictionary (PHP: "object"), regardless of whether the source data has contents or is empty?

Thanks!

(Yes, I googled around. That's how I learned about JSON_FORCE_OBJECT, but I was unable to find anything to help with this specific problem.)

0

2 Answers 2

1

Since there doesn't seem to be much activity on that question, here is a totally rudimentary/experimental idea:

<?php
abstract class JSONComplexType extends ArrayObject implements JsonSerializable {
    abstract protected function __key($key);
    abstract public function jsonSerialize();

    public function offsetSet ($key, $newval) {
        $offset = $this->__key($key);
        if ( is_null($offset) ) {
            trigger_error(get_class($this).': invalid key:'.var_export($key, true), E_USER_ERROR);
        }
        else {
            parent::offsetSet($offset, $newval);
        }
    }
}

class JSONArray extends JSONComplexType {
    protected function __key($key) {
        if ( is_null($key) ) {
            return $this->count();
        }
        else if ( !is_int($key) && !ctype_digit($key) ) {
            return null;
        }
        else {
            $key = intval($key);
            return $key <= $this->count() ? $key : null;
        }
    }

    public function jsonSerialize() {
        return $this->getArrayCopy();
    }
}

class JSONObject extends JSONComplexType {
    protected function __key($key) {
        return (string)$key;
    }

    public function jsonSerialize() {
        return (object)($this->getArrayCopy());
    }
}

function foo($in) {
    if ( is_object($in) ) {
        $retval = new JSONObject();
        foreach( get_object_vars($in) as $key=>$value ) {
            $retval[$key] =foo($value);
        }
    }
    else if ( is_array($in) ) {
        $retval = new JSONArray();
        foreach( array_values($in) as $key=>$value ) {
            $retval[$key] = foo($value);
        }
    }
    else {
        $retval = $in;
    }
    return $retval;
}

$in = '{
    "A":[
        {
            "X": 1,
            "Y": "z",
            "O": {},
            "AA":[ {},{},[],[1,2,3],{"I":"V"}]
        },
        {
            "X": 2,
            "Y": "Z",
            "AA": []
        }
    ]
}';

$json = foo( json_decode($in) );
echo "---- 1 ----\r\n", json_encode($json);

$json['A'][0]['AA'][1]['addObject']='New';
$json['A'][0]['AA'][2][]='New0';
$json['A'][0]['AA'][2][1]='New1';
$json['A'][0]['AA'][2][]='New2';
unset($json['A'][0]['AA'][3][0]);
unset($json['A'][0]['AA'][3][1]);
unset($json['A'][0]['AA'][3][2]);
unset($json['A'][0]['AA'][4]['I']);
echo "\r\n---- 2 ----\r\n", json_encode($json);

prints

---- 1 ----
{"A":[{"X":1,"Y":"z","O":{},"AA":[{},{},[],[1,2,3],{"I":"V"}]},{"X":2,"Y":"Z","AA":[]}]}
---- 2 ----
{"A":[{"X":1,"Y":"z","O":{},"AA":[{},{"addObject":"New"},["New0","New1","New2"],[],{}]},{"X":2,"Y":"Z","AA":[]}]}

The output seems to be ok, but the code would need quite some doing (and documentation ;-)). It's just something I came up with during lunch break...

Sign up to request clarification or add additional context in comments.

1 Comment

Well, this is a bit wilder than what I was hoping for, but it's better than "it can't be done", so I'm going to accept it. FWIW, I ended up just doing validation on the receiving end. That is, I have code similar to NSDictionary *myDict = [otherDict objectForKey: @"hopeItsADict"]; if ([myDict count] == 0) { myDict = [NSDictionary new]; } // guard against json mangling (There's more to it, my dicts are mutable, etc., but that's the gist of it.)
0

(Just copying my code from comment to VolkerK into an answer, for easier reading & copy/paste.)

I ended up just doing validation on the receiving end. That is, I have code similar to:

NSDictionary *myDict = [otherDict objectForKey: @"hopeItsADict"];
if ([myDict count] == 0)      // guard against json mangling 
{
    myDict = [NSDictionary new];
}

There's more to it, my dicts are mutable, etc., but that's the gist of it. This works because both NSDictionary and NSArray respond to count, so the if-check doesn't care which one it is (nor if it's nil.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.