What Types of Values Can Be Transmitted in HTTP Requests

http; request; type
996 words

The origin was me complaining in a frontend group about a colleague: requiring the frontend to pass Date type data to the backend, and then the backend returns data as Number type timestamp... (my blood pressure was really high at that time). Then a group member asked a great question: "Curious, how do you pass Date type?"

Me: "Good question! You got me stumped..."

Although I wasn't sure exactly how Date type data was being passed at that time, I knew from my previous practice that directly passing a Date type could make a normal request, so something must have happened in between. I used to just laugh it off as a cow, but now I'm going to investigate it.

HTTP JSON Request

In the vast majority of cases, I use the axios library to send requests, but they're all JSON data. So let's first look at how the most basic XMLHttpRequest sends JSON data, I asked AI to write a demo:

// Encapsulate XHR request function
function makeRequest(method, url, data = null) {
  return new Promise((resolve, reject) => {
    // Create XHR object
    const xhr = new XMLHttpRequest();

    // Open connection
    xhr.open(method, url, true);

    // Set request header
    xhr.setRequestHeader('Content-Type', 'application/json');

    // Handle load complete event
    xhr.onload = function() {
      if (this.status >= 200 && this.status < 300) {
        resolve(JSON.parse(xhr.response));
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };

    // Handle network error
    xhr.onerror = function() {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };

    // Send request
    xhr.send(data ? JSON.stringify(data) : null);
  });
}

// Usage examples
// GET request
makeRequest('GET', 'https://api.example.com/users')
  .then(data => console.log('Users:', data))
  .catch(error => console.error('Error:', error));

// POST request
const userData = { name: 'John Doe', email: 'john@example.com' };
makeRequest('POST', 'https://api.example.com/users', userData)
  .then(response => console.log('User created:', response))
  .catch(error => console.error('Error creating user:', error));

From the demo, we can see that when sending the request with xhr.send(data ? JSON.stringify(data) : null);, the data is processed by JSON.stringify, so the data type passed in the request is a JSON string.

Let's also look at how fetch sends requests:

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(userData)
})
  .then(response => response.json())
  .then(data => console.log('User created:', data))
  .catch(error => console.error('Error creating user:', error));

Same thing, fetch also processes data through JSON.stringify.

So the question is, what types of data can be included in a JSON string?

JSON Data Types

Let's first look at the definition on MDN:

JSON.stringify() converts values to their corresponding JSON format:

  • If the value being converted has a toJSON() method, that method defines what value will be serialized.
  • Properties of non-array objects are not guaranteed to appear in any particular order in the serialized string.
  • Wrapper objects for booleans, numbers, and strings are automatically converted to their corresponding primitive values during serialization.
  • undefined, any functions, and symbol values are ignored when they appear as property values in non-array objects, or converted to null when they appear in arrays. Functions and undefined are converted to undefined when converted alone, such as JSON.stringify(function()) or JSON.stringify(undefined).
  • Executing this method on an object containing circular references (where objects reference each other, forming an infinite loop) will throw an error.
  • All properties with symbol keys will be completely ignored, even if a replacer parameter explicitly specifies to include them.
  • Date objects call toJSON() to convert to a string (same as Date.toISOString()), so they are treated as strings.
  • NaN and Infinity format numbers and null are all treated as null.
  • Other types of objects, including Map/Set/WeakMap/WeakSet, only serialize enumerable properties.

from JSON.stringify() - JavaScript | MDN

From the above definition, we can conclude that the data types that can be included in a JSON string are:

  1. String
  2. Number
  3. Boolean
  4. Object
  5. Array
  6. null

So how is Date type data transmitted?

It will be converted to a string by the toJSON() method, so in the JSON string, Date type data is string type.

const date = new Date();
console.log(JSON.stringify(date)); // "2024-08-19T08:00:00.000Z"
// Equivalent to
console.log(date.toJSON()); // "2024-08-19T08:00:00.000Z"

So looking at it this way, Date type data can be passed directly, it's just that during the passing process it's converted to string type data.

HTTP Requests

Let's look at what types of HTTP requests there are.

Like the JSON request above:

  1. POST request with Content-Type: application/json
  2. GET request with Accept: application/json

Similar request types include:

  • XML request (Content-Type: application/xml)
  • Form Data request (Content-Type: application/x-www-form-urlencoded)
  • Multipart Form Data request (Content-Type: multipart/form-data)
  • Plain Text request (Content-Type: text/plain)

Let's compare these request types:

// Compare requests using Fetch API

// 1. JSON request
const jsonRequest = async (url, data) => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  });
  return response.json();
};

// 2. XML request
const xmlRequest = async (url, xmlString) => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/xml',
    },
    body: xmlString,
  });
  return response.text(); // XML responses are usually handled as text
};

// 3. Form Data request
const formDataRequest = async (url, data) => {
  const formData = new URLSearchParams(data);
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: formData,
  });
  return response.json();
};

// 4. Multipart Form Data request
const multipartFormDataRequest = async (url, data) => {
  const formData = new FormData();
  for (const [key, value] of Object.entries(data)) {
    formData.append(key, value);
  }
  const response = await fetch(url, {
    method: 'POST',
    body: formData, // No need to set Content-Type, browser will set it automatically
  });
  return response.json();
};

// 5. Plain Text request
const plainTextRequest = async (url, text) => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'text/plain',
    },
    body: text,
  });
  return response.text();
};

Comparative analysis:

  1. JSON request:

    • Pros: Clear data structure, easy to parse, widely supported
    • Cons: Doesn't support binary data, may be inefficient for large data
  2. XML request:

    • Pros: Supports complex data structures, self-describing
    • Cons: Relatively complex parsing, larger data size
  3. Form Data request:

    • Pros: Simple, good compatibility
    • Cons: Not suitable for complex data structures
  4. Multipart Form Data request:

    • Pros: Supports file uploads, can mix different types of data
    • Cons: Larger request body, not suitable for frequent small data transfers
  5. Plain Text request:

    • Pros: Simple, suitable for transmitting pure text data
    • Cons: Not suitable for structured data, parsing may require extra work

So JSON requests are widely used, but in specific scenarios, other types of requests also have their place.