4

On DD4T 2 Java version environment, I created view model class. But, when there is a embedded field in another embedded field, 2nd embedded field values are not mapped to view model instance, and I got following error. (1st embedded field's value is mapped to view model instance normaly.)

How can 2nd embedded field be mapped to view model?

2015-09-15 11:35:53,826 ERROR JsonModelConverter - Error setting field!
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'FieldType' that is to contain type id  (for class org.dd4t.contentmodel.impl.BaseField)
 at [Source: N/A; line: -1, column: -1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:841) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:140) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:92) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:132) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:41) ~[jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3023) [jackson-databind-2.4.3.jar:2.4.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1637) [jackson-databind-2.4.3.jar:2.4.3]
    at org.dd4t.databind.util.JsonUtils.renderComponentField(JsonUtils.java:46) ~[dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.deserializeGeneric(JsonModelConverter.java:302) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildField(JsonModelConverter.java:213) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildModelProperties(JsonModelConverter.java:117) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.convertSource(JsonModelConverter.java:94) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonDataBinder.buildModel(JsonDataBinder.java:177) [dd4t-debug.jar:na]
    at org.dd4t.databind.DataBindFactory.buildModel(DataBindFactory.java:71) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildModelForField(JsonModelConverter.java:286) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.checkTypeAndBuildModel(JsonModelConverter.java:271) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildField(JsonModelConverter.java:210) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildModelProperties(JsonModelConverter.java:117) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.convertSource(JsonModelConverter.java:94) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonDataBinder.buildModel(JsonDataBinder.java:177) [dd4t-debug.jar:na]
    at org.dd4t.databind.DataBindFactory.buildModel(DataBindFactory.java:71) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildModelForField(JsonModelConverter.java:286) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.checkTypeAndBuildModel(JsonModelConverter.java:271) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildField(JsonModelConverter.java:210) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.buildModelProperties(JsonModelConverter.java:117) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonModelConverter.convertSource(JsonModelConverter.java:94) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonDataBinder.buildModel(JsonDataBinder.java:177) [dd4t-debug.jar:na]
    at org.dd4t.databind.builder.json.JsonDataBinder.buildModels(JsonDataBinder.java:138) [dd4t-debug.jar:na]

Component XML source code and view model class are following.

Component:

<zzmemorytest xmlns="uuid:5db5a656-6dac-4300-8cc7-ebf76def7dc8">
    <text>1 st level text</text>
    <emb1>
        <text>2nd level text</text>
        <emb>
            <text>3rd level text</text>
        </emb>
    </emb1>
</zzmemorytest>

View model class:

@ViewModel(rootElementNames = "TestMemoryTestModel")
public class TestMemoryTestModel extends TridionViewModelBase {
    @ViewModelProperty(entityFieldName = "text")
    private String text;

    @ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb1")
    private TestMemoryTestEmb2Model emb1;

    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public TestMemoryTestEmb2Model getEmb1() {
        return emb1;
    }
    public void setEmb1(TestMemoryTestEmb2Model emb1) {
        this.emb1 = emb1;
    }

    @ViewModel(rootElementNames = "zzmemorytestemb2")
    public static class TestMemoryTestEmb2Model extends TridionViewModelBase {
        @ViewModelProperty(entityFieldName = "text")
        private String text;

        @ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb")
        private TestMemoryTestEmb1Model emb;

        public String getText() {
            return text;
        }
        public void setText(String text) {
            this.text = text;
        }
        public TestMemoryTestEmb1Model getEmb() {
            return emb;
        }
        public void setEmb(TestMemoryTestEmb1Model emb) {
            this.emb = emb;
        }
    }

    @ViewModel(rootElementNames = "zzmemorytestemb1")
    public static class TestMemoryTestEmb1Model extends TridionViewModelBase {
        @ViewModelProperty(entityFieldName = "text")
        private String text;

        public String getText() {
            return text;
        }
        public void setText(String text) {
            this.text = text;
        }
    }
}

UPDATE

After some investigation, it turns out that 2nd embedded field have to be FieldSet class. The embedded field's property looks like following.

@ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb")
private FieldSet emb;

But, 1st embedded field have to be TridionViewModelBase, not FieldSet.

UPDATE2

I succeeded to map 2nd embedded field value to model class. I added new model class which defines only property for embedded field and, the property's type is second embedded field model class. The new class is "TestMemoryTestEmb1EmbeddedModel" in following new source code. This mode structure is strange, but it works.

Component:

<zzmemorytest xmlns="uuid:5db5a656-6dac-4300-8cc7-ebf76def7dc8">
    <textone>textone lv1</textone>
    <emb1>
        <textembtwo>textembtwo lv2</textembtwo>
        <emb>
            <textembone>textembone lv3</textembone>
        </emb>
    </emb1>
</zzmemorytest>

View Model Class:

@ViewModel(rootElementNames = "TestMemoryTestModel")
public class TestMemoryTestModel extends TridionViewModelBase {
    @ViewModelProperty(entityFieldName = "textone")
    private String text;

    @ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb1")
    private TestMemoryTestEmb2Model emb1;

    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public TestMemoryTestEmb2Model getEmb1() {
        return emb1;
    }
    public void setEmb1(TestMemoryTestEmb2Model emb1) {
        this.emb1 = emb1;
    }

    @ViewModel(rootElementNames = "zzmemorytestemb2")
    public static class TestMemoryTestEmb2Model extends TridionViewModelBase {
        @ViewModelProperty(entityFieldName = "textembtwo")
        private String text;

        @ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb")
        private TestMemoryTestEmb1EmbeddedModel emb;

        public String getText() {
            return text;
        }
        public void setText(String text) {
            this.text = text;
        }
        public TestMemoryTestEmb1EmbeddedModel getEmb() {
            return emb;
        }
        public void setEmb(TestMemoryTestEmb1EmbeddedModel emb) {
            this.emb = emb;
        }
    }

    /*
     * This class is added!
     */
    @ViewModel(rootElementNames = "zzmemorytestemb1")
    public static class TestMemoryTestEmb1EmbeddedModel extends TridionViewModelBase {
         @ViewModelProperty(tridionFieldType = FieldType.EMBEDDED, entityFieldName = "emb")
        private TestMemoryTestEmb1Model emb;

        public TestMemoryTestEmb1Model getEmb() {
            return emb;
        }

        public void setEmb(TestMemoryTestEmb1Model emb) {
            this.emb = emb;
        }
    }

    @ViewModel(rootElementNames = "zzmemorytestemb1")
    public static class TestMemoryTestEmb1Model extends TridionViewModelBase {
        @ViewModelProperty(entityFieldName = "textembone")
        private String text;

        public String getText() {
            return text;
        }
        public void setText(String text) {
            this.text = text;
        }
    }
}

Regards,

  • Hi Hirokazu. Could you please log this as an issue here: github.com/dd4t/dd4t-2-java/issues ? Then we can have a look asap! – Raimond Sep 28 '15 at 14:42
  • It works after changing 2nd embedded field's property to FieldSet class. So I updated my question. I'm not sure this is good solution, and whether it's really impossible to map ViewModel class to 2nd embedded field. – Hirokazu Shikata Sep 29 '15 at 9:17
  • Well I'm not sure either. I've verified that you can have as much embedded fields as you want and deserialization works. My next step is to verify how this behaves with ViewModel classes. I'll keep you posted. – Raimond Sep 29 '15 at 13:33
  • I succeeded to map 2nd embedded field to view model class, so I added the source code to my question. Because The new model class looks strange, I'm not confident this is correct, but it's working. Also, I added this issue to GitHub. – Hirokazu Shikata Sep 30 '15 at 14:04
  • Hi, I will definitely look into it on the short term, but your latest code is exactly how it should be done if you want all your sub models to be view models based on your classes. It might actually be less of confusion if there were other properties in each embedded model. Then you see that the structure in creating viewmodels really is > if fieldtype == embedded, create a full new view model class for that and repeat infinitely. I'll give a definitive answer before end of this week :) – Raimond Sep 30 '15 at 15:33
1

Your issue got me thinking on how we did deserialisation of embedded fields and how that should work with using ViewModels. While your code is correct and will work, I did some refactoring in DD4T version 2.0.2 to make it a bit smoother.

If you don't want to use a Strongly Typed Model for this property, you can simply add the following to your class in version 2.0.2:

@ViewModelProperty
EmbeddedField embedded;

In your view you can then loop over the embedded values as shown in the example below:

<c:forEach var="embeddedField" items="${Generic.embedded.embeddedValues}">
    <%-- fieldkey here must be a valid content key in the data --%>
    ${embeddedField.content.fieldkey}
</c:forEach>

This being possible, setting up an Strongly Typed POJO is by far the easiest thing to do, but has some limitations. In deserializing the Json into POJO's for Embedded fields, only the values set in the EmbeddedValues node are used. This means that most of the metadata on the Embedded field is lost. Fortunately this usually is not really an issue.

To create ViewModels, as you have displayed in your example, you first need to create a separate class where the embedded field can be serialized into:

@ViewModel (rootElementNames = {"EmbeddableTest"})
public class EmbeddedOne extends TridionViewModelBase {
    @ViewModelProperty
    private String testfieldOne;
    @ViewModelProperty
    private EmbeddedTwo embeddableTwo;
}

The dd4t-databind frame work also handles nested embedded schemas, so let's create another one to match your example:

@ViewModel(rootElementNames = {"embeddableTestTwo"})
public class EmbeddedTwo extends TridionViewModelBase {
    @ViewModelProperty
    private String testfieldTwo;

    public String getTestfieldTwo () {
        return testfieldTwo;
    }
}

If you then change the ViewModelProperty of the parent class to:

@ViewModelProperty
List<EmbeddedOne> embedded

You will notice a couple of things:

  • The EmbeddedValues collection node basically now is the starting point to start deserializing. This is a big contrast with using the EmbeddedField class, where that same EmbeddedValues collection is inside the EmbeddedField
  • Since we now use the EmbeddedValues node as deserialization root and there usually is more than one embedded value coming from a Tridion Component, the handiest thing to do here is to define the embedded field as List embedded
  • It's then possible to use Strong types in your value collection as well, as shown above in EmbeddedOne and EmbeddedTwo classes

This way, it's a lot cleaner to read values from embedded fields. In JSTL, it is possible to do:

<c:forEach var="embeddedField" items="${Generic.embedded}">
    ${embeddedField.testfieldOne}
    ${embeddedField.embeddableTwo.testfieldTwo}
</c:forEach>

In summary, your code is correct in will work in version 2.0.1-beta. The only thing I would like to recommend is that you do not use static classes, as the databind framework always instantiates them!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.