2

I have AJAX responses where all key,value pairs are quoted. I want to remove the quotes from any values that are numbers. I need to do this globally, for all AJAX requests, with minimal effort.

I am using jQuery.getJSON for the majority of the requests, and I want a solution that doesn't require tweaking each of the current AJAX requests individually. (I don't want to add a JSON.parse(text,myreviver) call in every one of the existing $.getJSON calls.)

For example:

Received:    {"age":"31", "money": "-1.329"}
Want:        {"age": 31, "money": -1.329}

How can I do this, for all AJAX requests?

My current end-goal is to use JSON.parse(text, reviver) to process the data (thanks to this question). I don't know how to hook this into jQuery.ajax, though.

I have tried using ajaxSuccess(), but it doesn't appear to chain-process any data. For example:

$(document.ajaxSuccess) ( function(j) {
    JSON.parse(j, myReviver);
}

.getJSON(url, data, function(j) {
    // The data in this success function has not been through myReviver.
}

How can I:

  • use a reviver function in jQuery.getJSON, or
  • process the AJAX response in a global success function before it reaches other success functions?
simont
  • 68,704
  • 18
  • 117
  • 136

2 Answers2

3

You could override default text to json converter using ajaxSetup "globally, for all AJAX requests, with minimal effort."

$.ajaxSetup({
  converters: {
    // default was jQuery.parseJSON
    'text json': data => JSON.parse(data, numberReviver)
  }
})


$.getJSON('https://jsonplaceholder.typicode.com/users')
  .then(users => users.map(user => ({email: user.email, geo: user.address.geo})))
  .then(console.log)
  
function numberReviver(name, value) {
  if(typeof value === 'string' && /^[+-]?[0-9]+(?:\.[0-9]+)$/.test(value)) {
    console.log(`Using custom reviver for ${name}:${value}`)
    value = Number(value)
  }
  
  return value
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98
  • I didn't think of that -- I got stuck pursuing the `ajaxSuccess` route. This looks significantly cleaner. I've ended up implementing a `dataFilter`, although that has it's own set of potential problems. Thanks for the idea! – simont Jan 11 '19 at 04:54
1

You can overwrite .getJSON with your own method.

The native getJSON method can be seen here:

https://j11y.io/jquery/#v=git&fn=jQuery.getJSON

function (url, data, callback) {
  return jQuery.get(url, data, callback, "json");
}

It's a simple enough matter to take the callback passed to getJSON, pass your own callback to jQuery.get with 'text' instead of 'json', parse the JSON using the custom reviver, and then call the original callback.

A complication is that the second parameter passed to getJSON is optional (data to send with the request).

Assuming that you always use a success function (third and last argument), you can use rest parameters and pop() to get the passed success function. Create a custom success function that takes the text response and uses the custom JSON.parse, then calls the passed success function with the created object.

const dequoteDigits = json => JSON.parse(
  json,
  (key, val) => (
    typeof val === 'string' && /^[+-]?[0-9]+(?:\.[0-9]+)$/.test(val)
    ? Number(val)
    : val
  )
);
jQuery.getJSON = function (url, ...rest) {
  // normal parameters: url, data (optional), success
  const success = rest.pop();
  const customSuccess = function(textResponse, status, jqXHR) {
    const obj = dequoteDigits(textResponse);
    success.call(this, obj, status, jqXHR);
  };
  // spread the possibly-empty empty array "rest" to put "data" into argument list
  return jQuery.get(url, ...rest, customSuccess, 'text');
};

jQuery.getJSON('https://jsonplaceholder.typicode.com/users', (obj) => {
  console.log(obj);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

The difference in the snippet output above vs the URL requested

https://jsonplaceholder.typicode.com/users

is that the latitude and longitude property values have been transformed into numbers, rather than remaining strings.

If you ever use await as well without a success callback, then you'll need to overwrite the then property of the returned Promise, though dealing with the optional success callback makes the code's logic a lot uglier:

const dequoteDigits = json => JSON.parse(
  json,
  (key, val) => (
    typeof val === 'string' && /^[+-]?[0-9]+(?:\.[0-9]+)$/.test(val)
    ? Number(val)
    : val
  )
);
jQuery.getJSON = function (url, ...rest) {
  // normal parameters: url, data (optional), success (optional)
  const data = rest.length && typeof rest[0] !== 'function'
  ? [rest.shift()]
  : [];
  
  const newSuccessArr = typeof rest[0] === 'function'
  ? [function(textResponse, status, jqXHR) {
        const obj = dequoteDigits(textResponse);
        rest[0].call(this, obj, status, jqXHR);
      }
    ]
  : [];
  // spread the possibly-empty dataObj and newSuccessObj into the new argument list array
  const newArgs = [url, ...data, ...newSuccessArr, 'text'];
  
  const prom = jQuery.get.apply(this, newArgs);
  const nativeThen = prom.then;
  prom.then = function(resolve) {
    nativeThen.call(this)
      .then((res) => {
        const obj = dequoteDigits(this.responseText);
        resolve(obj);
      });
  };
  return prom;
};


jQuery.getJSON('https://jsonplaceholder.typicode.com/users', (obj) => {
  console.log(obj[0].address.geo);
});



(async () => {
  const obj = await jQuery.getJSON('https://jsonplaceholder.typicode.com/users');
  console.log(obj[0].address.geo);
  // console.log(obj);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320