Skip to content

Commit

Permalink
Allow specifying a replacement value for empty results
Browse files Browse the repository at this point in the history
This allows distinguishing a non-matching query vs a matched value that is actually empty.

Fixed #7
  • Loading branch information
kzu committed Sep 30, 2021
1 parent cc949d6 commit 2ca7cc1
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Devlooped.Json.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{58AD3669-734C-4A6F-B622-9F75D7E903C2}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.netconfig = .netconfig
.github\dependabot.yml = .github\dependabot.yml
readme.md = readme.md
EndProjectSection
EndProject
Expand Down
18 changes: 17 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ Parameters:

| Parameter | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| Content | Optional `string` parameter. Input/Output.<br/>Specifies the JSON input as a string. |
| Content | Optional `string` parameter.<br/>Specifies the JSON input as a string. |
| ContentPath | Optional `ITaskItem` parameter.<br/>Specifies the JSON input as a file path. |
| Empty | Optional `string` parameter.<br/>Value to use as a replacement for empty values matched in JSON. |
| Query | Required `string` parameter.<br/>Specifies the [JSONPath](https://goessner.net/articles/JsonPath/) expression. |
| Result | Output `ITaskItem[]` parameter.<br/>Contains the results that are returned by the task. |

Expand Down Expand Up @@ -86,6 +87,21 @@ These item metadata values could be read as MSBuild properties as follows, for e
In addition to the explicitly opted in object properties, the entire node is available
as raw JSON via the special `_` (single underscore) metadata item.

If the matched value is empty, no items (because items cannot be constructed with empty
identity) or property value will be returned. This makes it difficult to distinguish a
successfully matched empty value from no value matched at all. For these cases, it's
possible to specify an `Empty` value to stand-in for an empty (but successful) matched
result instead, which allow to distinguish both scenarios:

```xml
<JsonPeek Content="$(Json)" Empty="$empty" Query="$(Query)">
<Output TaskParameter="Result" PropertyName="Value" />
</JsonPeek>

<Error Condition="'$(Value)' == '$empty'" Text="The element $(Query) cannot have an empty value." />
```


![JsonPoke Icon](assets/img/jsonpoke.png) JsonPoke
============

Expand Down
9 changes: 9 additions & 0 deletions src/JsonPeek/JsonPeek.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ public class JsonPeek : Task
/// </summary>
public ITaskItem? ContentPath { get; set; }

/// <summary>
/// Specifies an optional value to use as a replacement for
/// empty values matched in JSON. This allows distinguishing
/// non-matching queries versus queries that match nodes but
/// contain an empty value.
/// </summary>
public string? Empty { get; set; }

/// <summary>
/// Specifies the JSONPath query.
/// </summary>
Expand Down Expand Up @@ -57,6 +65,7 @@ public override bool Execute()
Result = json.SelectTokens(Query)
// NOTE: we cannot create items with empty ItemSpec, so skip them entirely.
// see https://github.com/dotnet/msbuild/issues/3399
.Select(x => !string.IsNullOrEmpty(x.ToString()) ? x : Empty)
.Where(x => !string.IsNullOrEmpty(x.ToString()))
.SelectMany(x => x.AsItems()).ToArray();

Expand Down
16 changes: 15 additions & 1 deletion src/JsonPeek/readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[![Version](https://img.shields.io/nuget/vpre/JsonPeek.svg?color=royalblue)](https://www.nuget.org/packages/JsonPeek)
[![Downloads](https://img.shields.io/nuget/dt/JsonPeek.svg?color=green)](https://www.nuget.org/packages/JsonPeek)
[![License](https://img.shields.io/github/license/devlooped/json.svg?color=blue)](https://github.com/devlooped/json/blob/main/license.txt)
[![Build](https://github.com/devlooped/json/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/json/actions)

Usage:

Expand Down Expand Up @@ -68,12 +67,27 @@ These item metadata values could be read as MSBuild properties as follows, for e
In addition to the explicitly opted in object properties, the entire node is available
as raw JSON via the special `_` (single underscore) metadata item.

If the matched value is empty, no items (because items cannot be constructed with empty
identity) or property value will be returned. This makes it difficult to distinguish a
successfully matched empty value from no value matched at all. For these cases, it's
possible to specify an `Empty` value to stand-in for an empty (but successful) matched
result instead, which allow to distinguish both scenarios:

```xml
<JsonPeek Content="$(Json)" Empty="$empty" Query="$(Query)">
<Output TaskParameter="Result" PropertyName="Value" />
</JsonPeek>

<Error Condition="'$(Value)' == '$empty'" Text="The element $(Query) cannot have an empty value." />
```

## Parameters

| Parameter | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| Content | Optional `string` parameter.<br/>Specifies the JSON input as a string. |
| ContentPath | Optional `ITaskItem` parameter.<br/>Specifies the JSON input as a file path. |
| Empty | Optional `string` parameter.<br/>Value to use as a replacement for empty values matched in JSON. |
| Query | Required `string` parameter.<br/>Specifies the [JSONPath](https://goessner.net/articles/JsonPath/) expression. |
| Result | Output `ITaskItem[]` parameter.<br/>Contains the results that are returned by the task. |

Expand Down
1 change: 0 additions & 1 deletion src/JsonPoke/readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[![Version](https://img.shields.io/nuget/vpre/JsonPoke.svg?color=royalblue)](https://www.nuget.org/packages/JsonPoke)
[![Downloads](https://img.shields.io/nuget/dt/JsonPoke.svg?color=green)](https://www.nuget.org/packages/JsonPoke)
[![License](https://img.shields.io/github/license/devlooped/json.svg?color=blue)](https://github.com/devlooped/json/blob/main/license.txt)
[![Build](https://github.com/devlooped/json/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/json/actions)

Usage:

Expand Down
64 changes: 64 additions & 0 deletions src/Tests/Peek.targets
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,70 @@
<Error Condition="'$(Expected)' != '$(Actual)'" Text="Expected $(Expected) but was $(Actual)" />
</Target>

<Target Name="PeekEmptyValue">
<PropertyGroup>
<Json>
{
"hello": ""
}
</Json>
<Query>$.hello</Query>
<Expected></Expected>
</PropertyGroup>

<JsonPeek Content="$(Json)" Query="$(Query)">
<Output TaskParameter="Result" PropertyName="Actual" />
</JsonPeek>

<JsonPeek Content="$(Json)" Query="$(Query)">
<Output TaskParameter="Result" ItemName="Actual" />
</JsonPeek>

<Error Condition="'$(Expected)' != '$(Actual)'" Text="Expected $(Expected) but was $(Actual)" />
<Error Condition="@(Actual -> Count()) != 0" Text="Expected @(Actual) to be empty" />
</Target>

<Target Name="PeekEmptyNonDefaultValue">
<PropertyGroup>
<Json>
{
"hello": ""
}
</Json>
<Query>$.hello</Query>
<Expected>$empty</Expected>
</PropertyGroup>

<JsonPeek Content="$(Json)" Empty="$empty" Query="$(Query)">
<Output TaskParameter="Result" PropertyName="Actual" />
</JsonPeek>

<JsonPeek Content="$(Json)" Empty="$empty" Query="$(Query)">
<Output TaskParameter="Result" ItemName="Actual" />
</JsonPeek>

<Error Condition="'$(Expected)' != '$(Actual)'" Text="Expected $(Expected) but was $(Actual)" />
<Error Condition="@(Actual -> Count()) != 1" Text="Expected @(Actual) to have one item" />
</Target>

<Target Name="PeekNotFoundNonDefaultValue">
<PropertyGroup>
<Json>
{
"hello": ""
}
</Json>
<Query>$.foo</Query>
<Expected></Expected>
</PropertyGroup>

<JsonPeek Content="$(Json)" Empty="$empty" Query="$(Query)">
<Output TaskParameter="Result" PropertyName="Actual" />
</JsonPeek>

<Error Condition="'$(Expected)' != '$(Actual)'" Text="Expected $(Expected) but was $(Actual)" />
</Target>

<Target Name="PeekFileValues">
<JsonPeek ContentPath="sample.json" Query="$.immutable">
<Output TaskParameter="Result" PropertyName="Actual" />
Expand Down

0 comments on commit 2ca7cc1

Please sign in to comment.