x-stream/xstream

how to use Xstream convert from XML to javabean

Closed this issue · 3 comments

First,this is my entity class:
@XStreamAlias("Trade")
public class Trade {

@XStreamAlias("TradeAmount")
private String tradeAmount;

@XStreamOmitField
private String tradeCurrency;

@XStreamAlias("TradeTime")
@XStreamConverter(value = CustomDateConverter.class)
private Date tradeTime;

@XStreamAlias("Refuned")
@XStreamConverter(value= BooleanConverter.class,booleans={false},strings={"yes","no"})
private boolean refunded;
................

}

Second, the XML is what I want to convert:

      <Trade>
            <TradeAmount>USD100</TradeAmount> 
            <TradeTime>2022-07-07 14:25:24</TradeTime>
            <Refuned>no</Refuned>
             ........................
      </Trade>

Third,this is what I expect to achieve:
Trade(tradeAmount=100, tradeCurrency=USD, tradeTime=Thu Jul 07 14:25:24 CST 2022, refunded=false)

Very urgent, very important,who can help me quickly,thank you very much,best wishes

1/ XStream won't call this constructor of yours ever. Reflection based converters will inject the values directly into the instance, Javabean-based converter requires setters.

2/ Nevertheless, any of the generic converters of XStream provide a 1:1 relationship between an XML element (or attribute) to a field. In your example you want to split the value of one XML element into two fields, which is not supported out-of-the-box.

3/ You can handle this situation with a custom converter for your Trade class. See the example in the converter tutorial for the complex type. Here you can handle the XML elements on your own and you can decide yourself whether you want XStream to handle a field value or do it on your own. And you can decide on your own, how you create the required instance.

4/ These annotations are only important for the generic converters. If you write a custom converter for your type, you might also remove them (except of the one for the Trade class itself).

Hope this helps,
Jörg

I have saw the example in the converter tutorial for the complex type,and I have wrote a custom converter ,it can help me solve this problem ,but I expect a better method ,so,can you give me some another suggestions about it ? Hopefully, best wishes.

public class CustomAmtCurrencyConverter extends ReflectionConverter {

private final String[] splitField;
private final String[] assignField;

public CustomAmtCurrencyConverter(Mapper mapper, ReflectionProvider reflectionProvider, Class type, String splitField, String evalField) {
    super(mapper, reflectionProvider, type);
    this.splitField = StringUtils.split(splitField, ",");
    this.assignField = StringUtils.split(evalField, ",");
}

@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    Object result = this.instantiateNewInstance(reader, context);
    result = this.doUnmarshal(result, reader, context);
    try {
        for (int i = 0; i < splitField.length; i++) {
            Field tradeAmount = this.reflectionProvider.getFieldOrNull(result.getClass(), splitField[i]);
            if (tradeAmount != null) {
                tradeAmount.setAccessible(true);
                String tradeAmountValue = (String) tradeAmount.get(result);
                if (StringUtils.isNotBlank(tradeAmountValue)) {
                    tradeAmount.set(result, tradeAmountValue.substring(3));
                    Field currencyField = this.reflectionProvider.getFieldOrNull(result.getClass(), assignField[i]);
                    if (currencyField != null) {
                        this.reflectionProvider.writeField(result, assignField[i], tradeAmountValue.substring(0,3), result.getClass());
                    }
                }
            }
        }

    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    return this.serializationMembers.callReadResolve(result);
}

}

@XStreamConverter(value = CustomAmtCurrencyConverter.class, strings = {"tradeAmount", "tradeCurrency"})

Well, normally when you write a custom converter, you will handle all the (known) fields yourself, like it is done in the tutorial. To derive from the generic ReflectionConverter is unconventional, but it works. Do you have to use reflection also for your split fields, i.e. do you have more types with such a requirement?

However, if you can modify your model types, you could take a different approach by tweaking the serialization (for simplicity I left out all annotations) using writeReplace/readResolve methods:

private String transient tradeAmount;
private String transient tradeCurrency;
private String TradeAmount;

private Object writeReplace() {
  TradeAmount = tradeCurrency+tradeAmount;
  return this;
}
private Object readResolve() {
  tradeCurrency = TradeAmount.substring(0,3);
  tradeAmount = TradeAmount.substring(3);
  TradeAmount = null;
  return this;
}

You would use that way the helper field "TradeAmount" for serialization, where the two other fields are omitted, but keep the real data.