13

I have some objects that I'm parsing from json using native browser implementations. Some of the objects' properties are numbers. For the moment, the numbers are parse from json as strings and I use parseInt to cast the string to the int I need.

The problem is that I've got 23 objects I do this with and overall about 80 properties that I'm parsing to ints like this:

if (TheObject && TheObject.TheProperty) {
   TheObject.TheProperty = parseInt(TheObject.TheProperty, 10);
}

There are many lines of code that look very similar. Is there a way using prototypes or something to change the way the JSON.parse function works so that each time the parser runs it checks to see if a string property is actually an int and if so cast it directly as such?

Thanks.

5
  • How would you do that automagically if parseInt('1a', 10); returns 1? But anyway, even if property looks like a number - it doesn't mean it is numeric Commented May 12, 2012 at 12:47
  • In the cases where I run the parseInt function, I know it's a number. For case where the property might be a number but might also be 1a, you'd need to use jquery's IsNumeric function. Commented May 12, 2012 at 12:49
  • If the numerical character string are number according to the source then you can convert, otherwise not. Commented May 12, 2012 at 12:50
  • 2
    JSON.parse can handle this natively, but it would be better to send them as numbers in the first place. Is this out of the question? Commented May 12, 2012 at 12:58
  • ok, thanks guys for your answer. I'll keep in mind the reviver function for some other purpose. In the meantime, I'm reworking some server serialization implementations. Commented May 12, 2012 at 13:21

5 Answers 5

14

JSON.parse accepts a second argument in the form of a function that can do some post processing.

JSON.parse('{"p": "5"}', function(k, v) { 
    return (typeof v === "object" || isNaN(v)) ? v : parseInt(v, 10); 
});

I you don't want to process all numeric strings, then create a lookup table of the properties you do want.

var props = {"p":1, "some_prop":1, "another_prop":1};

JSON.parse('{"p": "5"}', function(k, v) { 
    return props.hasOwnProperty(k) ? parseInt(v, 10) : v; 
});
Sign up to request clarification or add additional context in comments.

5 Comments

Problem is, some things might pass the isNaN check, and still not be numbers: { "name": "Johnny Wonten", "login": "1.10", description: "One-Ten, geddit!?!11!" }
@Amadan: This is just an example of a test. OP could test against a lookup table of properties instead. Though I'm not not exactly sure what you mean. Which item in your example would pass that shouldn't.
In the example, login should still be a string; it is a coincidence that it looks like a number.
@Amadan: Yes, I'm assuming that all numeric strings should be processed as numbers. I couldn't be more precise without more info from OP. Could be that the lookup table would be more appropriate. I'll update.
@cliffsofinsanity This was just what I was looking for. The test itself isn't perfect but shows how it works, so I'll be using this plenty in the future. Thanks!
9

JSON can handle numbers as follow:

{
    "TheObject":{
        "TheProperty":5
    }
}

If your property was doublequoted then it's a string else it's a numeric, boolean (true and false values), null or just something that cause parse error.

See http://json.org/

2 Comments

ok, thanks, it does indeed work without the parsing; I just removed all the parsing code and it worked for most properties. For the ones it didn't work, I'm updating the server serializer to take care of the double-quote problem. Thanks.
Use online parser like this one if you not sure about your JSON or just for experiments.
4

If your data source can't be fixed (numbers should be passed as numbers, not as strings), you can pass JSON.parse a "reviver" function, which will receive each item as it's being processed. This gives you the option of transforming it:

// Create this once
var propsToConvert = {
    TheProperty: 1,
    TheOtherProperty: 1,
    YetAnotherProperty: 1,
    // ...and so on...
};

// Use it each time you parse
var obj = JSON.parse(str, function(key, value) {
    if (propsToConvert.hasOwnProperty(key)) {
        return parseInt(value, 10);
    }
    return value;
});

Live example | source

Or if the property names are not sufficiently unique (TheProperty doesn't always need handling, just when it's a property of TheObject), you can do this as a two-level check:

// Define the object names and their property names (once)
var propsToConvert = {
    TheObject: {
        TheProperty: 1,
        TheOtherProperty: 1,
        YetAnotherProperty: 1,
        // ...and so on...
    },
    AnotherObject: {
        // Other properties...
    }
};

// Use it each time you parse
var obj = JSON.parse(str, function(key, value) {
    var name, props;

    if (typeof value === "object") {
        props = propsToConvert[key];
        if (props) {
            for (name in props) {
                value[name] = parseInt(value[name], 10);
            }
        }
    }
});

(Revivers are called inside-out, so the properties will be on the object by the time you see the object's key; that's why we update them in place.)

You get the idea, there's a lot you can do with reviver functions.


Side note: parseInt, which I've used above, is fairly forgiving — possibly more forgiving than you want. For instance:

var a = parseInt('1a', 10); // 1, instead of NaN

If you're okay with strings like "0x10" being treated as hex, then:

var a = Number(str);

...which will give you NaN for invalid number strings (Number("1a") is NaN). Since JSON isn't meant to have hex numbers, if you're sure the broken data source won't encode them as hex, you're golden.

Otherwise, if you need decimal but you want to be strict, you'll need to do a regex on the string to make sure it matches the pattern for a valid decimal number (which is fairly complex, if you want to support all the stuff JavaScript numeric literals support).

5 Comments

"The problem is that I've got 23 objects I do this with and overall about 80 properties". I guess it's not about how to convert the properties, it's about doing it to 80 different ThePropertys. It is rather inconsequential in that light whether it's done by a reviver function, or by post-processing on a parsed object.
Using .hasOwnProperty will be safer than in, given the albeit slight chance that there's some property on the parsed JSON that shares a name with a property on Object.prototype. I just realized that Mozilla has a non-standard "watch" property.
@cliffsofinsanity: I'm not using for..in on anything received from the JSON. I'm using it on the objects within propsToConvert. If someone puts an enumerable property on Object.prototype, they should be taken out back and beaten with a copy of Flanagan's JavaScript: The Definitive Guide (picked because it's big and heavy). :-)
But I didn't say for-in. I said in, as in if (key in propsToConvert) {. If key happens to be toString for example, it will return true, running the parseInt on its value.
@cliffsofinsanity: Oh, sorry, I was looking at the wrong code block. Yeah, fair enough. (I've never liked that in is true for non-enumerable properties, but for..in doesn't loop over them; two different in tokens, I know, but...)
2

I don't think you'll be able to modify the parser, but instead of many similar lines of code, use an array of the properties and access them with the [] notation in a loop to parseInt() them. Of course, if you have access to the code producing the JSON, it is easier to change it to output ints properly unquoted.

// Array of properties you want to parse out
var parseUs = ['prop1','prop2','prop3','prop4'];

// Array of objects you need to parse them in
var objs = [obj1, obj2, obj3];

// Iterate over the objects
for (var i=0; i<objs.length; i++) {

  // And over the properties array
  for (var j=0; j<parseUs.length; j++) {
    // Parse out the int value if the object has the property
    if (objs[i].hasOwnProperty(parseUs[j]) {
      objs[i][parseUs[j]] = parseInt(parseUs[j], 10);
    }
  }
}

Note: This won't work if the objects share property names which are not int values in all of them. If that's the case, you would need to modify this to use an array of properties per object.

2 Comments

"I don't think you'll be able to modify the parser..." No need, that's what reviver functions are for.
You can't modify the parser. It's just native code for built-in-browser parser. You can write own one, but what's all this for?
1

@kbec gives the correct answer. If you don't have control over your data source, then you can use something like this:

function intify(obj, fields) {
  if (typeof(obj) == "undefined") return;
  var numFields = fields.length;
  for (var i = 0; i < numFields; i++) {
    var field = fields[i];
    if (typeof(obj[field]) != "undefined") {
      obj[field] = parseInt(obj[field], 10);
    }
  }
  return obj;
}

intify(obj, ['foo', 'bar']);
intify(obj.baz, ['boo']);

6 Comments

If you manually convert to int then you can use instead parseInt(obj[field],10); this one: parseInt(obj[field],10)||0. With this you're sure that your property is a number even if they value was NaN (not a number) you'he got just 0.
@kbec: Matter of opinion, and of the specific use case. However, in general, I'd much rather receive a NaN than a 0 for something that isn't a number but should be. Sweeping it under the rug as a 0 can lead to some painful debugging sessions.
@kbec: True, but then you hide the fact that the thing that was supposed to be a number, wasn't.
@Amadan: The problem with parseInt is that it's so forgiving. parseInt('1a', 10) is 1.
@Amadan: Yeah, it's all about choices. (And totally a side issue.)
|

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.