Skip to content

Unnecessary addition of "rest" property breaking marshaling #75

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

Closed
mike-marcacci opened this issue Nov 17, 2016 · 3 comments
Closed

Unnecessary addition of "rest" property breaking marshaling #75

mike-marcacci opened this issue Nov 17, 2016 · 3 comments

Comments

@mike-marcacci
Copy link

mike-marcacci commented Nov 17, 2016

Hi there, this may be related to #69 and highsource/jsonix#149, but has a slightly different manifestation.

Essentially, a sequence embedded in a choice that is not the only part of a parent sequence somehow causes the child's elements to be mapped to a nonexistant rest property. Unmarshalling works as expected, even with the strange mapping; but when trying to marshal the unmarshalled value back to XML (or when trying to marshal any expected data structure), the relevant properties are stripped.

Here's a simplified XSD that illustrates this case, but the real ones that show this problem live here.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="testxsd" xmlns:xp="testxsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

    <xs:simpleType name="string25Type">
        <xs:restriction base="xs:string">
            <xs:maxLength value="25" />
        </xs:restriction>
    </xs:simpleType>

    <xs:complexType name="extensionType">
        <xs:attribute name="extId" type="xp:string25Type"/>
    </xs:complexType>

    <xs:element name="exampleElement">
        <xs:complexType>
            <xs:complexContent>
                <xs:extension base="xp:extensionType">
                    <xs:sequence>
                        <xs:choice>
                            <xs:sequence>
                                <xs:element name="exampleId" type="xp:string25Type"/>
                                <xs:element name="constant" type="xp:string25Type" minOccurs="0"/>
                            </xs:sequence>
                            <xs:sequence>
                                <xs:element name="exampleName" type="xp:string25Type"/>
                                <xs:element name="constant" type="xp:string25Type" minOccurs="0"/>
                            </xs:sequence>
                        </xs:choice>
                        <xs:element name="aaa" type="xp:string25Type"/>
                        <xs:element name="bbb" type="xp:string25Type" minOccurs="0"/>
                    </xs:sequence>
                </xs:extension>
            </xs:complexContent>
        </xs:complexType>
    </xs:element>
</xs:schema>

This generates the following mapping; notice the surprise name: 'rest' piece:

{
  name: 'testxsd',
  defaultElementNamespaceURI: 'testxsd',
  typeInfos: [
    {
      localName: 'ExampleElement',
      typeName: null,
      baseTypeInfo: '.ExtensionType',
      propertyInfos: [
        {
          name: 'rest',
          required: true,
          minOccurs: 2,
          maxOccurs: 4,
          collection: true,
          mixed: false,
          allowDom: false,
          elementTypeInfos: [
            { elementName: 'aaa' },
            { elementName: 'exampleId' },
            { elementName: 'bbb' },
            { elementName: 'constant' },
            { elementName: 'exampleName' }
          ],
          type: 'elementRefs'
        }
      ]
    },
    {
      localName: 'ExtensionType',
      typeName: 'extensionType',
      propertyInfos: [
        {
          name: 'extId',
          attributeName:
          {
            localPart: 'extId'
          },
          type: 'attribute'
        }
      ]
    }
  ],
  elementInfos: [
    {
      elementName: 'exampleName',
      scope: '.ExampleElement'
    },
    {
      elementName: 'bbb',
      scope: '.ExampleElement'
    },
    {
      elementName: 'exampleId',
      scope: '.ExampleElement'
    },
    {
      elementName: 'constant',
      scope: '.ExampleElement'
    },
    {
      elementName: 'aaa',
      scope: '.ExampleElement'
    },
    {
      typeInfo: '.ExampleElement',
      elementName: 'exampleElement'
    }
  ]
}

When using this mapping, I get the following results:

var Jsonix = require('jsonix').Jsonix;
var mapping = require('./testxsd.js').testxsd;
var context = new Jsonix.Context([mapping], {
	mappingStyle: 'simplified',
	namespacePrefixes: {
		'testxsd': ''
	}
});

var marshaller = context.createMarshaller();
var unmarshaller = context.createUnmarshaller();


// this is how the marshaller SHOULD work:
console.log(marshaller.marshalString({
	name: {
		namespaceURI: 'testxsd',
		localPart: 'exampleElement'
	},
	value: {
		extId: '111',
		exampleId: '12345',
		aaa: 'some value'
	}
}));

// =>
// <exampleElement xmlns="testxsd" extId="111"/>




// this is how the marshaller "works":
console.log(marshaller.marshalString({
	name: {
		namespaceURI: 'testxsd',
		localPart: 'exampleElement'
	},
	value: {
		extId: '111',
		rest: [
			{exampleId: '12345'},
			{aaa: 'some value'}
		]
	}
}));


// =>
// <exampleElement xmlns="testxsd" extId="111"><exampleId>12345</exampleId><aaa>some value</aaa></exampleElement>




// the unmarshaller works fine:
console.log(JSON.stringify(unmarshaller.unmarshalString(`
	<exampleElement xmlns="testxsd" extId="111">
		<exampleId>12345</exampleId>
		<aaa>some value</aaa>
	</exampleElement>
`)));


// =>
// {"exampleElement":{"TYPE_NAME":"testxsd.ExampleElement","extId":"111","rest":[{"exampleId":"12345"},{"aaa":"some value"}]}}
@mike-marcacci
Copy link
Author

Also, for reference, here is a mapping that works as I would expect:

{
  name: 'testxsd',
  defaultElementNamespaceURI: 'testxsd',
  typeInfos: [
    {
      localName: 'ExampleElement',
      typeName: null,
      baseTypeInfo: '.ExtensionType',
      propertyInfos: [
        { name: 'aaa' },
        { name: 'exampleId' },
        { name: 'bbb' },
        { name: 'constant' },
        { name: 'exampleName' }
      ]
    },
    {
      localName: 'ExtensionType',
      typeName: 'extensionType',
      propertyInfos: [
        {
          name: 'extId',
          attributeName:
          {
            localPart: 'extId'
          },
          type: 'attribute'
        }
      ]
    }
  ],
  elementInfos: [
    {
      elementName: 'exampleName',
      scope: '.ExampleElement'
    },
    {
      elementName: 'bbb',
      scope: '.ExampleElement'
    },
    {
      elementName: 'exampleId',
      scope: '.ExampleElement'
    },
    {
      elementName: 'constant',
      scope: '.ExampleElement'
    },
    {
      elementName: 'aaa',
      scope: '.ExampleElement'
    },
    {
      typeInfo: '.ExampleElement',
      elementName: 'exampleElement'
    }
  ]
}

@highsource
Copy link
Owner

You get rest property because you have two distinct constant elements. The compiler can't make a decision to map them onto one property constant like you did, these are distinct element after all. You don't have:

                    <xs:sequence>
                        <xs:choice>
                                <xs:element name="exampleId" type="xp:string25Type"/>
                                <xs:element name="exampleName" type="xp:string25Type"/>
                        </xs:choice>
                         <xs:element name="constant" type="xp:string25Type" minOccurs="0"/>
                        <xs:element name="aaa" type="xp:string25Type"/>
                        <xs:element name="bbb" type="xp:string25Type" minOccurs="0"/>
                    </xs:sequence>

, you have two constant elements.

To work around this, use a binding file and jaxb:property customization to give a distinct name to one of the elements (or both).

Something like:

<jaxb:bindings version="1.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
    jaxb:extensionBindingPrefixes="xjc">

    <jaxb:bindings schemaLocation="my.xsd" 
        node="/xs:schema">
        <jaxb:bindings node="xs:element[@name='exampleElement']/....../xs:choice/xs:sequence[xs:element[@name='exampleId']]/xs:element[@name='constant']">
            <jaxb:property name="ExampleIdConstant"/>
        </jaxb:bindings>
        <jaxb:bindings node="xs:element[@name='exampleElement']/....../xs:choice/xs:sequence[xs:element[@name='exampleName']]/xs:element[@name='constant']">
            <jaxb:property name="ExampleNameConstant"/>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

@mike-marcacci
Copy link
Author

Hi @highsource - thanks so much for the quick reply and for pointing me in the right direction with the really helpful example. This totally makes sense, and I was able to successfully create a binding file that resolved all such conflicts.

As I was learning more about jaxb I stumbled across this configuration, which happened to work perfectly for my particular use case, but certainly won't for other cases (especially since it's global here):

<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
    jaxb:extensionBindingPrefixes="xjc">

    <jaxb:globalBindings generateElementProperty="false">
        <xjc:simple />
    </jaxb:globalBindings>
</jaxb:bindings>

I just figured I'd leave that here as well in case somebody else comes searching the issues with a similar situation.

Thanks again!

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

No branches or pull requests

2 participants