goxml2json icon indicating copy to clipboard operation
goxml2json copied to clipboard

Possible to force array?

Open gbminnock opened this issue 5 years ago • 4 comments

Hi, is it possible for force an array? For example i have the following xml:

<?xml version="1.0" encoding="UTF-8"?>
<rss>
    <channel>
        <item>
            <apps>
                <app>App One</app>
            </apps>
        </item>
        <item>
            <apps>
                <app>App Two</app>
                <app>App Three</app>
            </apps>
        </item>
    </channel>
</rss>

which converts to

{
  "rss": {
    "channel": {
      "item": [
        { "apps": { "app": "App One" } },
        { "apps": { "app": ["App Two", "App Three"] } }
      ]
    }
  }
}

The problem is that the Apps/app node is an object but the second & third Apps/app node is an array. I understand why given the structure, but is it possible to force the first node to also be an array? Tks, Gary

gbminnock avatar May 27 '19 19:05 gbminnock

Hi Gary,

I am really sorry for the delay. I haven't had much time to dedicate to this project lately.

Yes someone implemented this functionality on PR #16.

I think you can do something like that:

  xml := strings.NewReader(`...`)
  json, err := xj.Convert(
    xml, 
    xj.WithNodes(
	xj.NodePlugin("rss.channel.item.apps.app", xj.ToArray()),
    ),
  )

Let me know if it works.

basgys avatar Jun 20 '19 23:06 basgys

xj.NodePlugin("rss.channel.item.apps.app", xj.ToArray()),

By trying, it looks to me that the way it works is by addressing the parent object holding the array, that is

xj.NodePlugin("rss.channel.item.apps", xj.ToArray()),

The problem with this is when the parent object also has attributes.. this is not rendered correctly; you can check by this test case:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"strings"

	xj "github.com/basgys/goxml2json"
)

func X2J(value string) string {
	var xml []byte
	xml = []byte(value)
	xmlrd := strings.NewReader(string(xml))
	jsonbytes, err := xj.Convert(xmlrd, xj.WithNodes(
		xj.NodePlugin("foo.bar", xj.ToArray()),
	))
	if err != nil {
		log.Fatal(err)
		return ""
	}
	return jsonbytes.String()
}

func main() {
	xml := `<foo>
	<bar barname="bar">
		<zoo name="zoo"/>
	</bar>
	<bar barname="bar">
		<zoo name="zoo"/>
		<zoo name="zoo"/>
	</bar>
</foo>`
	fmt.Printf("XML:\n%v\n", xml)
	rawjson := X2J(xml)
	var prettyjson bytes.Buffer
	json.Indent(&prettyjson, []byte(rawjson), "", "  ")
	fmt.Printf("JSON:\n%v\n", prettyjson.String())
}

In this test, zoo is indeed forced into an array, but, because bar also has attributes (barname), those attributes get trapped in the forced translation as well:( is there any other way to avoid this?

The expected result I would like is the following:

{
  "foo": {
    "bar": [
      {
        "zoo": [
          {
            "-name": "zoo"
          }
        ],
        "-barname": "bar"
      },
      {
        "zoo": [
          {
            "-name": "zoo"
          },
          {
            "-name": "zoo"
          }
        ],
        "-barname": "bar"
      }
    ]
  }
}

whereas I currently get this:

{
  "foo": {
    "bar": [
      {
        "zoo": [
          {
            "-name": "zoo"
          }
        ],
        "-barname": [
          "bar"
        ]
      },
      {
        "zoo": [
          {
            "-name": "zoo"
          },
          {
            "-name": "zoo"
          }
        ],
        "-barname": "bar"
      }
    ]
  }
}

As you see, although zoo becomes an array, where the transformation is applied, also the attributes of bar are translated hence screwing things up

riccardomanfrin avatar Aug 01 '19 07:08 riccardomanfrin

I think I'm figuring it out, the ChildrenAlwaysAsArray property only gets applied to the first found path, not all paths. So in my example, the presence of repeated bar items is not addressed and only the first object of the bar array is found with ChildrenAlwaysAsArray set to true

I also think that a better approach would be to not indicate the parent whose children must be arrays, but instead the children that are array objects. This way you could selectively discriminate between properties and items (and items of different types)

riccardomanfrin avatar Aug 01 '19 12:08 riccardomanfrin

It may be an attribute array="true" to force element as array.

<?xml version="1.0" encoding="UTF-8"?>
<rss>
    <channel>
        <item>
            <apps>
                <app array="true">App One</app>
            </apps>
        </item>
        <item>
            <apps>
                <app>App Two</app>
                <app>App Three</app>
            </apps>
        </item>
    </channel>
</rss>
{
  "rss": {
    "channel": {
      "item": [
        {
          "apps": {
            "app": [
              "App One"
            ]
          }
        },
        {
          "apps": {
            "app": [
              "App Two",
              "App Three"
            ]
          }
        }
      ]
    }
  }
}

javadev avatar Feb 16 '20 14:02 javadev