Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Json to XML converter activity #153

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions activity/json2xml/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# JSON2XML

Activity for converting JSON object into XML.

For additional tuneups and syntax take a look at [MXJ](https://github.com/clbanning/mxj) Go package.


## Installation

### Flogo CLI

```bash
flogo install github.com/project-flogo/contrib/activity/json2xml
```

## Configuration

### Input

| Name | Type | Description |
|------------|---------|-------------------------------|
| json | object | Input object in JSON format |
| xmlRootTag | string | Optional name of XML root tag |


### Output

| Name | Type | Description |
|------|------|----------------|
| xml | byte | Raw XML Output |

Example converting raw XML byte array into string.
```
string.tostring($activity[JsonToXML].xmlData )
```

## Usage

```json
{
"id": "JsonToXML",
"name": "JsonToXML",
"activity": {
"ref": "github.com/project-flogo/contrib/activity/json2xml",
"input": {
"xmlRootTag": "",
"json": "{\"hello\":\"world\"}"
}
}
}
```

Output:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<hello>world</hello>
```
78 changes: 78 additions & 0 deletions activity/json2xml/activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Package json2xml activity for converting JSON object into XML
package json2xml

import (
"encoding/xml"
"fmt"
"github.com/clbanning/mxj"
"github.com/project-flogo/core/activity"
"github.com/project-flogo/core/support/log"
"strings"
)

type Activity struct{}

// init Flogo activity
func init() {
_ = activity.Register(&Activity{})
}

// Default xmlHeader
var xmlHeader = []byte(xml.Header)

// Precalculated size of default root tags <doc></doc> from MXJ package
var startDefaultRootTagSize = len(mxj.DefaultRootTag) + 2
var endDefaultRootTagSize = len(mxj.DefaultRootTag) + 3

var metadata = activity.ToMetadata(&Input{}, &Output{})

// Metadata for the Activity
func (ac *Activity) Metadata() *activity.Metadata {
return metadata
}

func (ac *Activity) Eval(ctx activity.Context) (bool, error) {
ctx.Logger().Debugf("Activity [%s] JSON2XML", ctx.Name())

input := &Input{}
var err = ctx.GetInputObject(input)
if err != nil {
return false, activity.NewError(fmt.Sprintf("Activity [%s] Can't get input JSON object - %s", ctx.Name(), err.Error()), "JSON2XML-01", nil)
}

var out []byte
out, err = convert(input.Json, input.XmlRootTag, ctx.Logger())
if err != nil {
return false, activity.NewError(fmt.Sprintf("Activity [%s] Error converting JSON object [%T] to XML - %s", ctx.Name(), input.Json, err.Error()), "JSON2XML-02", nil)
}

err = ctx.SetOutputObject(&Output{Xml: out})
if err != nil {
return false, activity.NewError(fmt.Sprintf("Activity [%s] Can't set output XML object - %s", ctx.Name(), err.Error()), "JSON2XML-03", nil)
}

ctx.Logger().Debugf("Activity [%s] JSON2XML completed", ctx.Name())
return true, nil
}

func convert(json map[string]interface{}, xmlRootTag string, log log.Logger) ([]byte, error) {
mxj.XMLEscapeChars(true)
var data []byte
var err error
if len(strings.TrimSpace(xmlRootTag)) == 0 {
data, err = mxj.AnyXml(json)
if json != nil && err == nil {
// remove default root tags added by mxj package
data = data[startDefaultRootTagSize : len(data)-endDefaultRootTagSize]
}
} else {
data, err = mxj.AnyXml(json, xmlRootTag)
}

var buf []byte
buf = append(buf, xmlHeader...)
buf = append(buf, data...)

log.Debugf("Converted JSON object [%T] to XML object [%T] with size of %d", json, buf, len(buf))
return buf, err
}
205 changes: 205 additions & 0 deletions activity/json2xml/activity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package json2xml

import (
"encoding/json"
"encoding/xml"
"fmt"
"github.com/clbanning/mxj"
"github.com/project-flogo/core/data/coerce"
"math"
"testing"

"github.com/project-flogo/core/activity"
"github.com/project-flogo/core/support/test"
"github.com/stretchr/testify/assert"
)

func TestRegister(t *testing.T) {

ref := activity.GetRef(&Activity{})
act := activity.Get(ref)

assert.NotNil(t, act)
}

func TestStructure(t *testing.T) {

type NestedData struct {
DataA string `json:"data-a" xml:"data-a"`
}

type Data struct {
Name string `json:"name" xml:"name"`
Special string `json:"spec_chars" xml:"spec_chars"`
List []string `json:"list" xml:"list"`
Number int `json:"number" xml:"number"`
Decimal float32 `json:"decimal" xml:"decimal"`
Utf8String string `json:"utf8" xml:"utf8"`
Nested NestedData `json:"nested" xml:"nested"`
}

var rawTestData = &Data{
Name: "Test Name",
Special: "<>&\"'",
List: []string{"one", "two", "three"},
Number: 43,
Decimal: math.Pi,
Utf8String: "नमस्ते दुनिया", // Hello World in Hindi
Nested: NestedData{
DataA: "nested data value",
},
}

ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
var jsonObject, _ = coerce.ToObject(rawTestData) // json object type for Flogo framework
t.Logf("Marshaled JSON: %s", jsonObject)

acInput := &Input{Json: jsonObject, XmlRootTag: "Data"}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
acOutput := &Output{}
tc.GetOutputObject(acOutput)
t.Logf("Marshaled XML: %s", string(acOutput.Xml))

var outputData = &Data{}
var err = xml.Unmarshal(acOutput.Xml, &outputData)
if err != nil {
t.Error(err)
return
}

assert.True(t, done)
assert.Equal(t, rawTestData, outputData)
}

func TestEvalNoXMLRoot(t *testing.T) {
// setup
ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
jsonString := `{"hello": "world"}`
var jsonObject map[string]interface{}
json.Unmarshal([]byte(jsonString), &jsonObject)

acInput := &Input{Json: jsonObject, XmlRootTag: ""}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
acOutput := &Output{}
tc.GetOutputObject(acOutput)

assert.True(t, done)
var expectedResult = []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hello>world</hello>")
assert.Equal(t, expectedResult, acOutput.Xml)
}

func TestEvalCustomXMLRoot(t *testing.T) {
// setup
ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
jsonString := `{"hello": "world"}`
var jsonObject map[string]interface{}
json.Unmarshal([]byte(jsonString), &jsonObject)

acInput := &Input{Json: jsonObject, XmlRootTag: "my"}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
acOutput := &Output{}
tc.GetOutputObject(acOutput)

assert.True(t, done)
assert.Contains(t, string(acOutput.Xml), "my")
}

func TestJsonContainingXML(t *testing.T) {
// setup
ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
jsonString := `{"xml": "<hello>world</hello>"}`
var jsonObject map[string]interface{}
json.Unmarshal([]byte(jsonString), &jsonObject)

acInput := &Input{Json: jsonObject}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
expectedResult :=
`<?xml version="1.0" encoding="UTF-8"?>` +
"\n" +
`<xml>&lt;hello&gt;world&lt;/hello&gt;</xml>`
acOutput := &Output{}
tc.GetOutputObject(acOutput)

assert.True(t, done)
assert.Equal(t, expectedResult, string(acOutput.Xml))
}

func TestEmptyCustomXMLRootTag(t *testing.T) {
// setup
ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
acInput := &Input{Json: nil, XmlRootTag: "root"}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
expectedResult :=
`<?xml version="1.0" encoding="UTF-8"?>` +
"\n" +
`<root/>`
acOutput := &Output{}
tc.GetOutputObject(acOutput)

assert.True(t, done)
assert.Equal(t, expectedResult, string(acOutput.Xml))
}

func TestEmptyDefaultXMLRootTag(t *testing.T) {
// setup
ac := &Activity{}
tc := test.NewActivityContext(ac.Metadata())

// given
acInput := &Input{Json: nil}
tc.SetInputObject(acInput)

// when
done, _ := ac.Eval(tc)

// then
expectedResult :=
`<?xml version="1.0" encoding="UTF-8"?>` +
"\n" +
fmt.Sprintf("<%s/>", mxj.DefaultRootTag)
acOutput := &Output{}
tc.GetOutputObject(acOutput)

assert.True(t, done)
assert.Equal(t, expectedResult, string(acOutput.Xml))
}
27 changes: 27 additions & 0 deletions activity/json2xml/descriptor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "flogo-json2xml",
"type": "flogo:activity",
"ref": "github.com/project-flogo/contrib/activity/json2xml",
"version": "1.0.0",
"title": "JSON2XML Activity",
"description": "Converts given JSON into XML",
"homepage": "https://github.com/project-flogo/contrib/tree/master/activity/json2xml",
"input": [
{
"name": "json",
"type": "object",
"required": true
},
{
"name": "xmlRootTag",
"type": "string",
"required": false
}
],
"output": [
{
"name": "xml",
"type": "bytes"
}
]
}
9 changes: 9 additions & 0 deletions activity/json2xml/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/project-flogo/contrib/activity/json2xml

go 1.16

require (
github.com/clbanning/mxj v1.8.4
github.com/project-flogo/core v1.6.0
github.com/stretchr/testify v1.7.1
)
Loading