# CSV vs JSON Diff — Compare Prices Without False Alerts (`mrtronson/typed-csv-json-diff`) Actor

Compare CSV and JSON snapshots to find added, removed, and changed records. Keep real price changes while ignoring equivalent number formats. Receive JSON and CSV reports. Up to 5,000 rows per side.

- **URL**: https://apify.com/mrtronson/typed-csv-json-diff.md
- **Developed by:** [Typed Diff](https://apify.com/mrtronson) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$150.00 / 1,000 delivered comparisons

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## CSV vs JSON Diff

Compare exported CSV files with JSON data while keeping real changes visible.

`"19.9900"` in a CSV and `19.99` in JSON can represent the same price. Declare `price` as a decimal field to treat them as equal. A change from `19.99` to `29.00` still appears in the report.

Use this for product-price snapshots, stock reports and recurring data exports that cross file formats.

### What you receive

- A JSON report listing added, removed and changed records, with the exact fields that changed.
- Original value types and comparison values for each changed field.
- A CSV change report.
- Counts of unchanged rows and differences suppressed by your declared rules.

The service compares data you supply. It does not fetch websites, infer missing values or verify that a source price is correct.

### Price

**$0.15 per successfully delivered comparison**, including a report with zero changes. One comparison can contain up to 5,000 rows on each side, subject to the limits below. No per-row charge or separate platform-usage charge. The active Apify pricing panel is authoritative.

An invalid comparison is rejected without the completion event. Your Apify account provides the charge history.

### Try the example

Supply this input in Apify Console or through the Actor API:

```json
{
  "before": {"format":"csv","data":"sku,price\nA,19.9900\nB,25.00\n"},
  "after": {"format":"json","data":[{"sku":"A","price":19.99},{"sku":"B","price":29}]},
  "keyFields":["sku"],
  "fieldRules":{"price":{"type":"decimal"}},
  "snapshotsComplete":true
}
```

Expected result: A is unchanged; B has a price change from 25 to 29. The comparison suppresses one representation-only difference.

Open **Output → JSON report** or **CSV changes** after the run. For API use, the run's default key-value store contains `OUTPUT` and `CHANGES.csv`. Use the synchronous key-value-store endpoint if you want the JSON returned directly. The default dataset contains one summary row with the report hash and counts of added, removed, changed and unchanged records. The detailed reports remain in the key-value store.

### Input

Each side contains `format` (`csv` or `json`) and `data`. CSV data is a UTF-8 string; JSON data is an array of flat objects or a JSON-array string. CSV accepts an explicit `delimiter`: comma, semicolon or tab.

`keyFields` identifies records. Multiple fields form a compound key. Missing, empty or duplicate normalized keys are rejected; the service never silently chooses a winner. Identifiers keep leading zeros by default.

Fields are compared exactly unless you provide a rule:

| Type | Rule | Effect |
|---|---|---|
| Decimal | `{"type":"decimal"}` | Treats decimal strings and JSON numbers as equivalent without rounding. |
| Decimal comma | `{"type":"decimal","decimalSeparator":","}` | Parses comma decimals in strings. JSON numbers remain numeric. Grouping separators are not accepted. |
| Text | `{"type":"text","trim":true,"caseFold":true}` | Applies only the specified whitespace/case changes. |
| Date | `{"type":"date","beforeFormat":"DD/MM/YYYY","afterFormat":"YYYY-MM-DD"}` | Compares explicitly formatted calendar dates. Also supports `MM/DD/YYYY`. |
| Exact | `{"type":"exact"}` | Preserves differences between strings, numbers, booleans, null and missing fields. |

For typed fields, `nullable:true` explicitly allows null. Empty text is not automatically null or zero. `ignoreFields` excludes named fields completely; use it only for fields whose changes do not matter. A key or typed field cannot also be ignored.

`snapshotsComplete:true` is your confirmation that both snapshots are complete. Check upstream pagination, errors and row limits first. The service cannot verify that declaration and does not infer deletions from partial snapshots.

### Limits

- 1 MiB for the complete JSON input, including both snapshots.
- 5,000 rows per side; 50 columns per row; 100,000 cells combined.
- Flat scalar cells only; 8,192 characters per text cell.
- At most 4 MiB of JSON report; split larger work into separate comparisons.
- No URLs, XLSX files, nested objects, fuzzy matching, currency conversion or guessed date/number formats.
- Maximum 256 MiB memory, 8 seconds in the comparison engine and 55 seconds total in the Actor process.

### Frequently asked questions

**Does normalization hide genuine changes?** Rules define which differences you want treated as equivalent. Decimal normalization preserves numerical value exactly. Text case folding can intentionally hide case changes, so it is opt-in. The report records your rules.

**Can this recover digits that were already rounded away?** No. Supply precise decimals as strings when the sending system cannot preserve numerical precision.

**Will a retry charge me again?** Retrying/resurrecting the same Apify run uses one stable charge key. Starting a new run creates a new order, even for identical data. Save the run ID and retrieve its existing output after a timeout; do not blindly start another run.

**What if the payment acknowledgement is lost?** The report is saved before charging. Reuse the same run ID. A billing event accepted by Apify is distinct from payment settlement. Support can reconcile an uncertain event from the run ledger.

**Where is my data stored?** Input and output are in your run's Apify storage, subject to your retention and sharing settings. The code reads no other customer stores and sends no data to external analytics or model providers. Do not make confidential runs public. Apify's platform access and retention policies still apply.

**What should I include in a support issue?** The error code, run ID and a small synthetic example. Do not post private source files or credentials in a public issue. Duplicate-key or formatting errors usually require correcting the input. Billing disputes and unsupported edge cases may need a human response.

### Getting started

Run the included synthetic example, then replace both snapshots with your own data. Set the record key and declare decimal fields such as price and stock. Confirm that both snapshots are complete before running.

Use this Actor after your scraper or export step to compare product prices, inventory and recurring reports across CSV and JSON. Retrieve the saved JSON or CSV report to drive your own alerts or review workflow.

# Actor input Schema

## `before` (type: `object`):

Object with format (csv or json) and data. URLs are not supported.

## `after` (type: `object`):

Object with format (csv or json) and data.

## `keyFields` (type: `array`):

Unique record identity, e.g. sku. Compound keys are supported.

## `fieldRules` (type: `object`):

Explicit decimal, text or date rules. Unlisted fields use exact comparison.

## `ignoreFields` (type: `array`):

Entirely exclude fields such as scrapedAt. Do not ignore meaningful prices.

## `snapshotsComplete` (type: `boolean`):

The included synthetic example is complete. When replacing it, verify upstream pagination, errors and row limits before confirming. The service cannot independently verify completeness.

## Actor input object example

```json
{
  "before": {
    "format": "csv",
    "data": "sku,price,stock\nA,19.9900,10\nB,25.00,4\nC,9.50,0\nD,12.00,2\n"
  },
  "after": {
    "format": "json",
    "data": [
      {
        "sku": "A",
        "price": 19.99,
        "stock": 10
      },
      {
        "sku": "B",
        "price": 29,
        "stock": 4
      },
      {
        "sku": "C",
        "price": 9.5,
        "stock": 0
      },
      {
        "sku": "E",
        "price": 15,
        "stock": 5
      }
    ]
  },
  "keyFields": [
    "sku"
  ],
  "fieldRules": {
    "price": {
      "type": "decimal"
    },
    "stock": {
      "type": "decimal"
    }
  },
  "snapshotsComplete": true
}
```

# Actor output Schema

## `report` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `error` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "before": {
        "format": "csv",
        "data": "sku,price,stock\nA,19.9900,10\nB,25.00,4\nC,9.50,0\nD,12.00,2\n"
    },
    "after": {
        "format": "json",
        "data": [
            {
                "sku": "A",
                "price": 19.99,
                "stock": 10
            },
            {
                "sku": "B",
                "price": 29,
                "stock": 4
            },
            {
                "sku": "C",
                "price": 9.5,
                "stock": 0
            },
            {
                "sku": "E",
                "price": 15,
                "stock": 5
            }
        ]
    },
    "keyFields": [
        "sku"
    ],
    "fieldRules": {
        "price": {
            "type": "decimal"
        },
        "stock": {
            "type": "decimal"
        }
    },
    "snapshotsComplete": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrtronson/typed-csv-json-diff").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "before": {
        "format": "csv",
        "data": """sku,price,stock
A,19.9900,10
B,25.00,4
C,9.50,0
D,12.00,2
""",
    },
    "after": {
        "format": "json",
        "data": [
            {
                "sku": "A",
                "price": 19.99,
                "stock": 10,
            },
            {
                "sku": "B",
                "price": 29,
                "stock": 4,
            },
            {
                "sku": "C",
                "price": 9.5,
                "stock": 0,
            },
            {
                "sku": "E",
                "price": 15,
                "stock": 5,
            },
        ],
    },
    "keyFields": ["sku"],
    "fieldRules": {
        "price": { "type": "decimal" },
        "stock": { "type": "decimal" },
    },
    "snapshotsComplete": True,
}

# Run the Actor and wait for it to finish
run = client.actor("mrtronson/typed-csv-json-diff").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "before": {
    "format": "csv",
    "data": "sku,price,stock\\nA,19.9900,10\\nB,25.00,4\\nC,9.50,0\\nD,12.00,2\\n"
  },
  "after": {
    "format": "json",
    "data": [
      {
        "sku": "A",
        "price": 19.99,
        "stock": 10
      },
      {
        "sku": "B",
        "price": 29,
        "stock": 4
      },
      {
        "sku": "C",
        "price": 9.5,
        "stock": 0
      },
      {
        "sku": "E",
        "price": 15,
        "stock": 5
      }
    ]
  },
  "keyFields": [
    "sku"
  ],
  "fieldRules": {
    "price": {
      "type": "decimal"
    },
    "stock": {
      "type": "decimal"
    }
  },
  "snapshotsComplete": true
}' |
apify call mrtronson/typed-csv-json-diff --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrtronson/typed-csv-json-diff"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/B1JVjb2YeGBWHtBNW/builds/JOrTdvBJEfIFAOHJ6/openapi.json
