Skip to content

Fix package json deserialization 'engines' property#1629

Merged
grvillic merged 2 commits intomainfrom
users/grvillic/FixNpmLockSerialization
Feb 4, 2026
Merged

Fix package json deserialization 'engines' property#1629
grvillic merged 2 commits intomainfrom
users/grvillic/FixNpmLockSerialization

Conversation

@grvillic
Copy link
Collaborator

@grvillic grvillic commented Feb 4, 2026

Context

Recently System.Text.Json serialization library was introduced, this required significant changes in the repository to ensure backwards compatibility with Newtonsoft behaviors.

Issue

As I was validating unrelated work, I found that certain NPM package-lock json files can error out if the "engines" property is declared as an array instead of a dictionary. engines field historically had more flexibility. In very old npm/package.json specs (pre-npm 1.0), engines could be specified as:

  • An array: "engines": ["node >= 0.8"]
  • An object: "engines": { "node": ">= 0.8" }

The array format was deprecated long ago, but some very old packages still have it in their published metadata, and npm preserves this when generating package-lock.json files. See sample package with an array https://registry.npmjs.org/concat-stream/1.4.6, when official doc currently expects an object see NPM engines

Error log

[14:41:26 INF] Could not parse JSON from C:\Temp\BuildAgent\_work\1\s\projects\npm\express\package-lock.json file.
System.Text.Json.JsonException: The JSON value could not be converted to System.Collections.Generic.IDictionary`2[System.String,System.String]. Path: $.engines | LineNumber: 5 | BytePositionInLine: 18.
   at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
   at System.Text.Json.Serialization.JsonDictionaryConverter`3.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TDictionary& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)

Solution

Use the serialization utility that was introduced for other classes, see PackageJsonEnginesConverter utility in #1572

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.4%. Comparing base (8bb4237) to head (a6cfea2).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1629     +/-   ##
=======================================
+ Coverage   90.3%   90.4%   +0.1%     
=======================================
  Files        440     441      +1     
  Lines      38130   38314    +184     
  Branches    2347    2347             
=======================================
+ Hits       34465   34673    +208     
+ Misses      3183    3159     -24     
  Partials     482     482             

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a deserialization issue for the engines property in npm package-lock.json files by applying the existing PackageJsonEnginesConverter to PackageLockV2Package and PackageLockV3Package classes. The converter handles both the modern object format and legacy array format for the engines field, preventing deserialization errors when encountering old npm packages.

Changes:

  • Added [JsonConverter(typeof(PackageJsonEnginesConverter))] attribute to the Engines property in both PackageLockV2Package and PackageLockV3Package classes
  • Created comprehensive test suite covering object format, array format (including vscode detection), null values, missing fields, and serialization round-trips for both V2 and V3 package classes

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/Microsoft.ComponentDetection.Detectors/npm/Contracts/PackageLockV2Package.cs Added JsonConverter attribute to Engines property to handle array and object formats
src/Microsoft.ComponentDetection.Detectors/npm/Contracts/PackageLockV3Package.cs Added JsonConverter attribute to Engines property to handle array and object formats
test/Microsoft.ComponentDetection.Detectors.Tests/npm/Contracts/PackageLockPackageEnginesTests.cs Added comprehensive test coverage for engines property deserialization in both V2 and V3 package formats

@grvillic grvillic enabled auto-merge (squash) February 4, 2026 01:48
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

👋 Hi! It looks like you modified some files in the Detectors folder.
You may need to bump the detector versions if any of the following scenarios apply:

  • The detector detects more or fewer components than before
  • The detector generates different parent/child graph relationships than before
  • The detector generates different devDependencies values than before

If none of the above scenarios apply, feel free to ignore this comment 🙂

@grvillic grvillic merged commit 6748194 into main Feb 4, 2026
28 checks passed
@grvillic grvillic deleted the users/grvillic/FixNpmLockSerialization branch February 4, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants