Advanced KQL: Advanced parse_json().
The evidence was there.
It just was not sitting in a normal column.
Authentication methods. Alert entities. Device details. Cloud metadata. Policy outcomes.
To most analysts, it looked like unreadable text. Agent Foskett knew it was structured JSON waiting to be explored. Sometimes the most valuable evidence is not missing. It is simply hidden one level deeper.
Lesson overview
Learn how to parse nested JSON, extract dynamic properties, expand arrays and turn complex Microsoft Defender XDR telemetry into readable investigation columns.
Why advanced parse_json() matters
The advanced JSON mindset
Step 1 — Parse a dynamic field once
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
let TimeFrame = 7d;
DeviceEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| project Timestamp,
DeviceName,
ActionType,
ParsedFields
| take 50Step 2 — Access individual JSON properties
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let TimeFrame = 7d;
DeviceEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| extend EvidenceType = tostring(ParsedFields.EvidenceType),
DetectionSource = tostring(ParsedFields.DetectionSource),
PolicyName = tostring(ParsedFields.PolicyName)
| project Timestamp,
DeviceName,
ActionType,
EvidenceType,
DetectionSource,
PolicyNameStep 3 — Navigate nested JSON objects
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
let TimeFrame = 7d;
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(RawEventData)
| extend Raw = parse_json(RawEventData)
| extend UserAgent = tostring(Raw.UserAgent),
AppDisplayName = tostring(Raw.Application.DisplayName),
ResourceName = tostring(Raw.TargetResource.DisplayName)
| project Timestamp,
AccountDisplayName,
ActionType,
Application,
AppDisplayName,
ResourceName,
UserAgentStep 4 — Use bracket notation for awkward property names
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let TimeFrame = 7d;
EmailEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AuthenticationDetails)
| extend Auth = parse_json(AuthenticationDetails)
| extend SPF = tostring(Auth["SPF"]),
DKIM = tostring(Auth["DKIM"]),
DMARC = tostring(Auth["DMARC"]),
CompositeAuth = tostring(Auth["Composite Authentication"])
| project Timestamp,
SenderFromAddress,
RecipientEmailAddress,
SPF,
DKIM,
DMARC,
CompositeAuthStep 5 — Convert parsed values into useful types
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
let TimeFrame = 14d;
DeviceEvents
| where Timestamp > ago(TimeFrame)
| extend ParsedFields = parse_json(AdditionalFields)
| extend Score = toint(ParsedFields.RiskScore),
FirstSeen = todatetime(ParsedFields.FirstSeen),
Indicator = tostring(ParsedFields.Indicator)
| where Score >= 50
| project Timestamp,
DeviceName,
ActionType,
Indicator,
Score,
FirstSeen
| order by Score descStep 6 — Handle missing properties safely
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 7d;
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| extend Raw = parse_json(RawEventData)
| extend Country = tostring(Raw.Location.Country),
City = tostring(Raw.Location.City),
LocationSummary = iff(isempty(Country), "Unknown location", strcat(City, ", ", Country))
| project Timestamp,
AccountDisplayName,
ActionType,
Application,
IPAddress,
LocationSummaryStep 7 — Expand arrays after parsing
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
let TimeFrame = 7d;
AlertEvidence
| where Timestamp > ago(TimeFrame)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| extend EvidenceItems = todynamic(ParsedFields.Evidence)
| mv-expand EvidenceItems
| extend EvidenceType = tostring(EvidenceItems.Type),
EvidenceName = tostring(EvidenceItems.Name),
EvidenceValue = tostring(EvidenceItems.Value)
| project Timestamp,
AlertId,
EvidenceType,
EvidenceName,
EvidenceValueStep 8 — Parse AuthenticationDetails for email investigations
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
let TimeFrame = 30d;
EmailEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AuthenticationDetails)
| extend Auth = parse_json(AuthenticationDetails)
| extend SPF = tostring(Auth.SPF),
DKIM = tostring(Auth.DKIM),
DMARC = tostring(Auth.DMARC),
CompAuth = tostring(Auth.CompositeAuthentication)
| where DMARC has "fail" or SPF has "fail" or DKIM has "fail"
| project Timestamp,
SenderFromAddress,
SenderMailFromAddress,
RecipientEmailAddress,
Subject,
SPF,
DKIM,
DMARC,
CompAuth,
NetworkMessageIdStep 9 — Build readable investigation columns
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
let TimeFrame = 7d;
DeviceEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| extend InvestigationSource = tostring(ParsedFields.Source),
RuleName = tostring(ParsedFields.RuleName),
ThreatName = tostring(ParsedFields.ThreatName),
RemediationStatus = tostring(ParsedFields.RemediationStatus)
| project Timestamp,
DeviceName,
ActionType,
InvestigationSource,
RuleName,
ThreatName,
RemediationStatus
| order by Timestamp descStep 10 — Wrap JSON parsing into reusable logic
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
let ParsedDeviceEvidence = (Lookback:timespan) {
DeviceEvents
| where Timestamp > ago(Lookback)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| extend EvidenceSource = tostring(ParsedFields.Source),
EvidenceRule = tostring(ParsedFields.RuleName),
EvidenceStatus = tostring(ParsedFields.Status)
| project Timestamp,
DeviceName,
ActionType,
EvidenceSource,
EvidenceRule,
EvidenceStatus
};
ParsedDeviceEvidence(14d)
| summarize Events = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, EvidenceRule
| order by Events descCommon parse_json() mistakes
Investigation checklist
Related Agent Foskett Academy lessons
Coming next
Final thought
Advanced KQL Advanced parse_json in Microsoft Defender XDR
Agent Foskett Academy Lesson 126 teaches defenders how to use parse_json() for advanced KQL investigations in Microsoft Defender XDR, including nested JSON objects, arrays, dynamic fields and readable investigation columns.
Learn parse_json for Microsoft security investigations
Advanced parse_json() techniques help Microsoft security analysts extract hidden evidence from AdditionalFields, AuthenticationDetails, RawEventData, AlertEvidence and other dynamic Defender XDR telemetry fields.
Microsoft Defender XDR JSON parsing tutorial
This lesson explains how to parse JSON once, access nested properties, use bracket notation, convert values, handle missing fields, expand arrays with mv-expand and build reusable parsing logic for threat hunting.
