From 42120e13414b2bf2a6a6338b6119f0df4117a5b7 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 15 Jun 2024 17:04:48 +1000 Subject: [PATCH] Unique now works on maps and arrays #2068 --- pkg/yqlib/doc/operators/unique.md | 46 ++++++++++++++++++++++++++++++- pkg/yqlib/operator_unique.go | 29 ++++++++++++++----- pkg/yqlib/operator_unique_test.go | 18 +++++++++++- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/pkg/yqlib/doc/operators/unique.md b/pkg/yqlib/doc/operators/unique.md index a1bc443c..bc1d051d 100644 --- a/pkg/yqlib/doc/operators/unique.md +++ b/pkg/yqlib/doc/operators/unique.md @@ -63,7 +63,29 @@ will output - ~ ``` -## Unique array object fields +## Unique array objects +Given a sample.yml file of: +```yaml +- name: harry + pet: cat +- name: billy + pet: dog +- name: harry + pet: cat +``` +then +```bash +yq 'unique' sample.yml +``` +will output +```yaml +- name: harry + pet: cat +- name: billy + pet: dog +``` + +## Unique array of objects by a field Given a sample.yml file of: ```yaml - name: harry @@ -85,3 +107,25 @@ will output pet: dog ``` +## Unique array of arrays +Given a sample.yml file of: +```yaml +- - cat + - dog +- - cat + - sheep +- - cat + - dog +``` +then +```bash +yq 'unique' sample.yml +``` +will output +```yaml +- - cat + - dog +- - cat + - sheep +``` + diff --git a/pkg/yqlib/operator_unique.go b/pkg/yqlib/operator_unique.go index 823ad3f1..b7e78587 100644 --- a/pkg/yqlib/operator_unique.go +++ b/pkg/yqlib/operator_unique.go @@ -23,7 +23,7 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN candidate := el.Value.(*CandidateNode) if candidate.Kind != SequenceNode { - return Context{}, fmt.Errorf("Only arrays are supported for unique") + return Context{}, fmt.Errorf("only arrays are supported for unique") } var newMatches = orderedmap.NewOrderedMap() @@ -34,12 +34,9 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN return Context{}, err } - keyValue := "null" - - if rhs.MatchingNodes.Len() > 0 { - first := rhs.MatchingNodes.Front() - keyCandidate := first.Value.(*CandidateNode) - keyValue = keyCandidate.Value + keyValue, err := getUniqueKeyValue(rhs) + if err != nil { + return Context{}, err } _, exists := newMatches.Get(keyValue) @@ -59,3 +56,21 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN return context.ChildContext(results), nil } + +func getUniqueKeyValue(rhs Context) (string, error) { + keyValue := "null" + + if rhs.MatchingNodes.Len() > 0 { + first := rhs.MatchingNodes.Front() + keyCandidate := first.Value.(*CandidateNode) + keyValue = keyCandidate.Value + if keyCandidate.Kind != ScalarNode { + var err error + keyValue, err = encodeToString(keyCandidate, encoderPreferences{YamlFormat, 0}) + if err != nil { + return "", err + } + } + } + return keyValue, nil +} diff --git a/pkg/yqlib/operator_unique_test.go b/pkg/yqlib/operator_unique_test.go index fe28de56..9ad08e6d 100644 --- a/pkg/yqlib/operator_unique_test.go +++ b/pkg/yqlib/operator_unique_test.go @@ -33,13 +33,29 @@ var uniqueOperatorScenarios = []expressionScenario{ }, }, { - description: "Unique array object fields", + description: "Unique array objects", + document: `[{name: harry, pet: cat}, {name: billy, pet: dog}, {name: harry, pet: cat}]`, + expression: `unique`, + expected: []string{ + "D0, P[], (!!seq)::[{name: harry, pet: cat}, {name: billy, pet: dog}]\n", + }, + }, + { + description: "Unique array of objects by a field", document: `[{name: harry, pet: cat}, {name: billy, pet: dog}, {name: harry, pet: dog}]`, expression: `unique_by(.name)`, expected: []string{ "D0, P[], (!!seq)::[{name: harry, pet: cat}, {name: billy, pet: dog}]\n", }, }, + { + description: "Unique array of arrays", + document: `[[cat,dog], [cat, sheep], [cat,dog]]`, + expression: `unique`, + expected: []string{ + "D0, P[], (!!seq)::[[cat, dog], [cat, sheep]]\n", + }, + }, { skipDoc: true, document: `[{name: harry, pet: cat}, {pet: fish}, {name: harry, pet: dog}]`,