nlpjsonaimachine-learning

NER & Sentiment Analysis: The JSON Behind Classic NLP

·9 min read·AI & JSON

NLP existed before chatbots, and it has its own JSON shapes

Large language models get most of the attention now, but classic NLP tasks — named entity recognition, sentiment analysis, part-of-speech tagging — power a huge amount of production software that has nothing to do with chat: search relevance, content moderation, resume parsing, customer feedback analysis. These tasks predate LLMs by decades and have their own well-established JSON output conventions, used by spaCy, NLTK, Hugging Face pipelines, and cloud NLP APIs (AWS Comprehend, Google Natural Language). If your only NLP experience is prompting a chat model, these shapes will look refreshingly structured — no parsing prose, just typed fields.

Named Entity Recognition: spans, not sentences

NER identifies real-world entities — people, organizations, locations, dates — in text and reports exactly where in the string they occur, not just what they are:

json
{
  "text": "Ada Lovelace worked with Charles Babbage in London in 1843.",
  "entities": [
    { "text": "Ada Lovelace", "label": "PERSON", "start": 0, "end": 12 },
    { "text": "Charles Babbage", "label": "PERSON", "start": 26, "end": 41 },
    { "text": "London", "label": "GPE", "start": 45, "end": 51 },
    { "text": "1843", "label": "DATE", "start": 55, "end": 59 }
  ]
}

The start/end character offsets are the whole point — they let you highlight the exact substring in a UI, redact just that span for PII scrubbing, or link it to a knowledge base entry, all without re-searching the text for the entity string (which could appear multiple times with different meanings).

python
# spaCy — the de facto standard NLP library
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Ada Lovelace worked with Charles Babbage in London in 1843.")

entities = [
    {"text": ent.text, "label": ent.label_, "start": ent.start_char, "end": ent.end_char}
    for ent in doc.ents
]

Common entity labels you'll see across libraries: PERSON, ORG (organization), GPE (geopolitical entity — countries, cities), DATE, MONEY, PERCENT — though exact label sets differ slightly between spaCy, Stanford NLP, and cloud providers.

Sentiment analysis: a score, not just a label

Modern sentiment JSON gives you more than "positive/negative" — a continuous score plus a magnitude (how strongly the text expresses sentiment, regardless of direction) and often per-sentence breakdowns:

json
{
  "document": {
    "sentiment": "positive",
    "score": 0.87,
    "magnitude": 1.2
  },
  "sentences": [
    { "text": "The checkout flow is fast.", "sentiment": "positive", "score": 0.9 },
    { "text": "But the search is completely broken.", "sentiment": "negative", "score": -0.8 }
  ]
}

score typically ranges from -1 (very negative) to 1 (very positive); magnitude is intensity independent of polarity — a long, emotionally neutral document and a short, emotionally flat one both score near-zero magnitude, while a short but intense complaint scores high magnitude despite being brief. The document-level score alone often lies: a review that's half glowing and half furious can average out to a deceptively neutral document score — the sentences breakdown is what reveals the real, mixed sentiment.

Part-of-speech tagging and dependency parsing

POS tagging labels every token's grammatical role — useful for grammar checking, keyword extraction, and as a preprocessing step for other NLP tasks:

json
{
  "tokens": [
    { "text": "Ada", "pos": "PROPN", "tag": "NNP", "dep": "nsubj" },
    { "text": "codes", "pos": "VERB", "tag": "VBZ", "dep": "ROOT" },
    { "text": "quickly", "pos": "ADV", "tag": "RB", "dep": "advmod" }
  ]
}

pos is the universal, cross-language part of speech (PROPN = proper noun, VERB, ADV); tag is the finer-grained, language/treebank-specific tag (Penn Treebank tags like NNP, VBZ for English); dep is the token's grammatical relationship to its head word in a dependency parse. Most applications only need pos; tag and dep matter for grammar-aware tooling.

Cloud NLP APIs: the same ideas, vendor-specific fields

AWS Comprehend, Google Natural Language, and Azure Text Analytics all return broadly the same shapes with different field names — a useful reminder that the *concepts* (entities as spans, sentiment as a score) are universal even when the exact JSON keys aren't:

json
// AWS Comprehend — DetectEntities response (abridged)
{
  "Entities": [
    { "Text": "Ada Lovelace", "Type": "PERSON", "Score": 0.99, "BeginOffset": 0, "EndOffset": 12 }
  ]
}

Using these shapes in a real pipeline

A common architecture: run NER over support tickets to auto-tag mentioned products, run sentiment analysis to prioritize angry customers, and store both alongside the original text:

json
{
  "ticketId": "tk_4471",
  "text": "My Pro plan renewal failed and support hasn't responded in 3 days.",
  "entities": [
    { "text": "Pro plan", "label": "PRODUCT", "start": 3, "end": 11 }
  ],
  "sentiment": { "label": "negative", "score": -0.82, "magnitude": 1.4 },
  "priority": "high"
}

priority: "high" here is derived downstream from the sentiment score plus the negative magnitude crossing a threshold — a pattern worth generalizing: NLP output JSON is rarely the end product, it's an intermediate signal that business logic acts on.

Validating and debugging NLP JSON

  1. Inspect entity spans visually. Paste a large NER output into JSONKit's JSON Explorer to navigate dozens of entities without losing your place.
  2. Diff pipeline versions. When you upgrade a model or library version, run the same input through both and JSON Diff the outputs — entity boundaries and labels can shift subtly between model versions.
  3. Define a schema for your pipeline's output. If entities feed a database or downstream service, lock the shape down with a JSON Schema so a library upgrade that adds or renames a field doesn't silently break consumers.

Frequently asked questions

Classic NER models (spaCy, cloud NLP APIs) are fast, cheap, and return precise character-offset spans with confidence scores. LLM-based extraction is more flexible (custom entity types via a prompt) but slower, costlier per call, and doesn't natively give you exact character offsets unless you specifically ask for them.

The score alone can't distinguish "mildly positive throughout" from "no strong opinion at all" — both might average near zero. Magnitude captures the intensity of opinion separately from its direction, and per-sentence scores reveal mixed sentiment a single document score would hide.

Some NER formats (common in raw model output before post-processing) tag each token as B- (beginning of an entity), I- (inside/continuation), or O (outside any entity) rather than returning clean spans directly — post-processing merges consecutive B/I tokens into the span-based JSON shown above.

Loosely — PERSON, ORG, and DATE are common across spaCy, Stanford NLP, and most cloud APIs, but the full label set and exact naming differ per library and per language model, so don't assume portability without checking the specific model's documentation.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.