Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Resolving Mixed Data Type Errors in JSON

Working with JSON data often involves dealing with mixed data types—scenarios where the same property contains different types of values across multiple objects or records. These inconsistencies can lead to parsing errors, validation failures, and unexpected behavior in applications. This article explores common mixed data type errors in JSON and provides practical solutions for resolving them.

1. Understanding Mixed Data Type Errors

Mixed data type errors occur when a property that should contain a consistent data type instead contains different types across different instances. These inconsistencies typically arise from:

  • Data collected from multiple sources with different standards
  • Schema evolution without proper data migration
  • User-generated content with insufficient validation
  • Legacy systems with inconsistent data handling
  • Automatic type coercion in the data pipeline

2. Common Mixed Data Type Scenarios

Let's explore some common scenarios where mixed data types create problems:

2.1 Numbers as Strings

Problematic JSON:

{
  "products": [
    {
      "id": 1001,
      "price": 19.99,
      "quantity": 5
    },
    {
      "id": "1002",
      "price": "24.99",
      "quantity": "3"
    }
  ]
}

Problem: The second product has ID, price, and quantity as strings instead of numbers, which can break calculations, comparisons, and sorting operations.

2.2 Inconsistent Array vs. Single Value

Problematic JSON:

{
  "users": [
    {
      "name": "Alice",
      "tags": ["developer", "designer"]
    },
    {
      "name": "Bob",
      "tags": "manager"
    }
  ]
}

Problem: The first user has tags as an array, but the second user has a single string. This inconsistency breaks code that expects to iterate over the tags array.

2.3 Missing Values Represented Differently

Problematic JSON:

{
  "employees": [
    {
      "name": "Carol",
      "department": "Engineering",
      "manager": "Dave"
    },
    {
      "name": "Eve",
      "department": "Marketing",
      "manager": null
    },
    {
      "name": "Frank",
      "department": "Sales"
    }
  ]
}

Problem: Missing managers are represented inconsistently—null for Eve and completely omitted for Frank. This requires different handling for each case.

2.4 Boolean Values in Different Formats

Problematic JSON:

{
  "settings": [
    {
      "feature": "darkMode",
      "enabled": true
    },
    {
      "feature": "notifications",
      "enabled": "yes"
    },
    {
      "feature": "analytics",
      "enabled": 1
    }
  ]
}

Problem: The boolean "enabled" property is represented as a boolean, string, and number in different objects, making consistent feature flag checking difficult.

3. Impact of Mixed Data Type Errors

These inconsistencies can have significant effects on your applications:

  • Runtime errors when code expects a specific type (e.g., array.forEach() on a string)
  • Incorrect calculations (e.g., "1" + "2" = "12" instead of 3)
  • Schema validation failures when enforcing strict typing
  • Sorting inconsistencies when mixing strings and numbers
  • Security vulnerabilities when type coercion is exploited
  • Data loss during transformations that assume consistent types

4. Strategies for Detecting Mixed Data Types

Before fixing mixed data type issues, you need to identify them:

4.1 Using Schema Validation

// JSON Schema example that would catch type inconsistencies
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "products": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "price": { "type": "number" },
          "quantity": { "type": "integer" }
        },
        "required": ["id", "price", "quantity"]
      }
    }
  }
}

4.2 JavaScript Type Checking Functions

/**
 * Scan JSON data for mixed data types
 * @param {Object} data - The parsed JSON data to check
 * @param {string} [parentPath=''] - Used for recursion to track property path
 * @returns {Array} List of detected type inconsistencies
 */
function detectMixedTypes(data, parentPath = '') {
  const issues = [];
  const typeMap = new Map();
  
  // For arrays of objects, check each property across all objects
  if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object') {
    // Create a map of property name -> set of types
    data.forEach((item, index) => {
      Object.entries(item).forEach(([key, value]) => {
        const type = Array.isArray(value) ? 'array' : typeof value;
        const path = `${parentPath}[${index}].${key}`;
        
        if (!typeMap.has(key)) {
          typeMap.set(key, new Map([[type, path]]));
        } else {
          typeMap.get(key).set(type, path);
        }
      });
    });
    
    // Check for properties with multiple types
    typeMap.forEach((typeToPath, key) => {
      if (typeToPath.size > 1) {
        issues.push({
          property: key,
          types: Array.from(typeToPath.keys()),
          examples: Array.from(typeToPath.entries())
        });
      }
    });
    
    // Recursively check nested arrays and objects
    data.forEach((item, index) => {
      if (typeof item === 'object' && item !== null) {
        const nestedIssues = detectMixedTypes(item, `${parentPath}[${index}]`);
        issues.push(...nestedIssues);
      }
    });
  }
  
  // For objects, recursively check each property
  else if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
    Object.entries(data).forEach(([key, value]) => {
      const path = parentPath ? `${parentPath}.${key}` : key;
      
      if (typeof value === 'object' && value !== null) {
        const nestedIssues = detectMixedTypes(value, path);
        issues.push(...nestedIssues);
      }
    });
  }
  
  return issues;
}

5. Solutions for Resolving Mixed Data Types

Let's explore various approaches to fix mixed data type issues:

5.1 Type Normalization

Normalizing data types ensures consistency across your dataset:

Normalizing Numbers:

/**
 * Normalize numeric values in an object or array
 * @param {Object|Array} data - The data to normalize
 * @returns {Object|Array} Normalized data with consistent numeric types
 */
function normalizeNumbers(data) {
  if (Array.isArray(data)) {
    return data.map(item => normalizeNumbers(item));
  }
  
  if (typeof data !== 'object' || data === null) {
    // Convert string numbers to actual numbers
    if (typeof data === 'string' && !isNaN(data) && data.trim() !== '') {
      // If string contains decimal point, convert to float, otherwise to integer
      return data.includes('.') ? Number(data) : Number(data);
    }
    return data;
  }
  
  const result = {};
  for (const [key, value] of Object.entries(data)) {
    result[key] = normalizeNumbers(value);
  }
  return result;
}

// Example usage
const normalizedData = normalizeNumbers(jsonData);

Normalizing Arrays:

/**
 * Ensure values that should be arrays are consistently arrays
 * @param {Object} data - The data object to process 
 * @param {Array<string>} arrayFields - List of fields that should always be arrays
 * @returns {Object} Data with consistent array fields
 */
function normalizeArrayFields(data, arrayFields) {
  if (!data || typeof data !== 'object') {
    return data;
  }
  
  if (Array.isArray(data)) {
    return data.map(item => normalizeArrayFields(item, arrayFields));
  }
  
  const result = { ...data };
  
  arrayFields.forEach(field => {
    if (field in result) {
      // If not already an array, convert to a single-item array
      if (!Array.isArray(result[field])) {
        result[field] = result[field] === null || result[field] === undefined 
          ? [] 
          : [result[field]];
      }
    }
  });
  
  // Process nested objects
  for (const [key, value] of Object.entries(result)) {
    if (typeof value === 'object' && value !== null) {
      result[key] = normalizeArrayFields(value, arrayFields);
    }
  }
  
  return result;
}

// Example usage
const data = normalizeArrayFields(jsonData, ['tags', 'categories', 'permissions']);

Normalizing Boolean Values:

/**
 * Convert different boolean representations to actual booleans
 * @param {Object} data - The data to normalize
 * @param {Array<string>} booleanFields - Fields that should be treated as booleans
 * @returns {Object} Data with consistent boolean values
 */
function normalizeBooleanFields(data, booleanFields) {
  if (!data || typeof data !== 'object') {
    return data;
  }
  
  if (Array.isArray(data)) {
    return data.map(item => normalizeBooleanFields(item, booleanFields));
  }
  
  const result = { ...data };
  
  // Process specified boolean fields
  booleanFields.forEach(field => {
    if (field in result) {
      const value = result[field];
      
      // Convert various representations to boolean
      if (typeof value === 'string') {
        const lowercased = value.toLowerCase().trim();
        result[field] = ['true', 'yes', 'y', '1', 'on'].includes(lowercased);
      } else if (typeof value === 'number') {
        result[field] = value !== 0;
      }
      // Already boolean, no change needed
    }
  });
  
  // Process nested objects
  for (const [key, value] of Object.entries(result)) {
    if (typeof value === 'object' && value !== null) {
      result[key] = normalizeBooleanFields(value, booleanFields);
    }
  }
  
  return result;
}

// Example usage
const data = normalizeBooleanFields(jsonData, ['enabled', 'active', 'isAdmin']);

5.2 Handling Missing or Null Values

Consistent handling of missing values is crucial:

Normalizing Null and Undefined:

/**
 * Ensure consistent handling of missing values
 * @param {Object} data - The data to normalize
 * @param {Object} defaultValues - Map of field names to their default values
 * @param {boolean} preferNull - If true, use null for missing values; if false, delete the property
 * @returns {Object} Data with consistent handling of missing values
 */
function normalizeMissingValues(data, defaultValues = {}, preferNull = true) {
  if (!data || typeof data !== 'object') {
    return data;
  }
  
  if (Array.isArray(data)) {
    return data.map(item => normalizeMissingValues(item, defaultValues, preferNull));
  }
  
  const result = { ...data };
  
  // Apply default values where specified
  for (const [field, defaultValue] of Object.entries(defaultValues)) {
    if (!(field in result) || result[field] === undefined || result[field] === null) {
      result[field] = defaultValue;
    }
  }
  
  // Handle other fields based on preference
  for (const [key, value] of Object.entries(result)) {
    if (value === undefined) {
      if (preferNull) {
        result[key] = null;
      } else {
        delete result[key];
      }
    } else if (typeof value === 'object' && value !== null) {
      result[key] = normalizeMissingValues(value, defaultValues, preferNull);
    }
  }
  
  return result;
}

// Example usage
const data = normalizeMissingValues(jsonData, {
  manager: null,
  department: "Unassigned",
  salary: 0
}, true);

5.3 Implementing Type Conversion at Boundaries

Enforcing types at application boundaries helps maintain consistency:

API Response Processing:

/**
 * Process API response to ensure consistent types
 * @param {Object} response - The API response data
 * @returns {Object} Processed data with consistent types
 */
function processApiResponse(response) {
  // Apply multiple normalization functions
  let processed = { ...response };
  
  // Normalize numbers in specific fields
  processed = normalizeNumbers(processed);
  
  // Ensure array fields are always arrays
  processed = normalizeArrayFields(processed, ['tags', 'categories']);
  
  // Convert boolean-like values to actual booleans
  processed = normalizeBooleanFields(processed, ['active', 'verified']);
  
  // Handle missing values with defaults
  processed = normalizeMissingValues(processed, {
    createdAt: null,
    updatedAt: null,
    status: 'unknown'
  });
  
  return processed;
}

// Usage in API fetch
async function fetchData() {
  const response = await fetch('/api/data');
  const rawData = await response.json();
  return processApiResponse(rawData);
}

6. Preventing Mixed Data Types at Source

The best solution is to prevent type inconsistencies from occurring in the first place:

Client-Side Validation:

  • Validate form input types before submission
  • Use typed form controls (number inputs for numeric data)
  • Apply client-side schema validation
  • Format data consistently before sending to APIs

Server-Side Enforcement:

  • Implement strong type validation on API endpoints
  • Use typed frameworks and ORM models
  • Add database constraints for data types
  • Apply JSON Schema validation on incoming data

7. Example: Complete Type Consistency System

Here's a comprehensive approach to managing data types in a JSON-based system:

Type Definition and Enforcement:

// 1. Define your data model with explicit types
const UserSchema = {
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0 },
    isActive: { type: 'boolean' },
    roles: { 
      type: 'array',
      items: { type: 'string' }
    },
    metadata: { 
      type: 'object',
      additionalProperties: true
    }
  },
  required: ['id', 'name', 'email']
};

// 2. Create a validation function using a JSON Schema validator
import Ajv from 'ajv';
const ajv = new Ajv({ coerceTypes: true });
const validateUser = ajv.compile(UserSchema);

// 3. Apply the validator to incoming data
function processUserData(userData) {
  // Try to coerce types and validate
  const valid = validateUser(userData);
  
  if (!valid) {
    throw new Error(`Invalid user data: ${ajv.errorsText(validateUser.errors)}`);
  }
  
  // If coercion worked, the data now has consistent types
  return userData;
}

// 4. Use error boundaries for graceful failure
try {
  const user = processUserData(incomingData);
  saveUser(user);
} catch (error) {
  logValidationError(error);
  showUserFriendlyError('The user data contains invalid values. Please check the form.');
}

8. Best Practices for Data Type Management

  1. Document expected types: Clearly specify the expected type for each field in your API documentation.
  2. Use schema validation: Implement JSON Schema or equivalent validation at every data entry point.
  3. Create type boundary layers: Normalize data types at application boundaries (API clients, data access layers).
  4. Be consistent with missing values: Decide whether to use null values or omit properties, and stick to it.
  5. Write defensive code: Always check types before performing type-specific operations.
  6. Use typed languages or TypeScript: Leverage compile-time type checking where possible.
  7. Implement automated tests: Create tests specifically for checking data type consistency.
  8. Consider data migrations: When schema changes occur, migrate existing data to maintain consistency.

Pro Tip

When working with third-party APIs or legacy systems that might send inconsistent data types, implement an "adapter layer" that normalizes incoming data before it reaches your core application logic. This creates a clean boundary that shields your application from external inconsistencies.

9. Conclusion

Mixed data type errors in JSON can cause subtle bugs that are difficult to track down. By implementing consistent type detection, normalization, and validation strategies, you can ensure your application handles JSON data reliably regardless of its source. Remember that prevention is always better than cure—enforce strict typing at all data entry points to minimize the need for type fixing later.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool