Java关于xml序列化的自定义字段名称转换
在业务需要的情况下,偶尔需要使用到xml的序列化,如接入微信公众号时,推送的消息都是xml格式。有部分同学使用的是手动拼写xml,这个感觉不太爽,还是喜欢序列化工具,本文推荐使用XStream;
话不多说,开始第一步引入包(使用的Maven)
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency>
然后我们创建一个简单实体对象,在创建时,字段遵从驼峰式,如下
public class TextMessage {
private String toUserName;
private String fromUserName;
private Long createTime;
private String msgType;
private Long msgId;
private String content;
/*此处省略了getter && setter*/
}
xstream使用时,先创建对象如
TextMessage msg = new TextMessage();
msg.setContent("content");
msg.setFromUserName("from");
msg.setToUserName("to");
msg.setCreateTime(1L);
msg.setMsgType("text");
msg.setMsgId(1L);
XStream xstream=XStream();
xstream.toXML(msg);
得到的结果是
<com.hanson.pojo.TextMessage> <toUserName>to</toUserName> <fromUserName>from</fromUserName> <createTime>1</createTime> <msgType>text</msgType> <msgId>1</msgId> <content>content</content> </com.hanson.pojo.TextMessage>
可以看到有两点不符合微信的要求:
1)根节点是class的全路径,期望是xml ;
2)元素节点名首字母需要大写(当前是声明的字段驼峰式)
针对第一步,我们可以使用XStream的alias,如
xstream.alias("xml", msg.getClass());
xstream.toXML(msg);
结果为
<xml> <toUserName>to</toUserName> <fromUserName>from</fromUserName> <createTime>1</createTime> <msgType>text</msgType> <msgId>1</msgId> <content>content</content> </xml>
下面需要修改元素节点的首字母,查找源码可以看到XStream的构造函数可传入HierarchicalStreamDriver,而实现这个接口可以传入NameCoder,如下接口描述
public interface NameCoder { /** * Encode an object name for a node in the target format. * * @param name the name of the object data * @return the node name in the target format * @since 1.4 */ String encodeNode(String name); /** * Encode a meta-data name for an attribute in the target format. * * @param name the name of the meta-data * @return the attribute name in the target format * @since 1.4 */ String encodeAttribute(String name); /** * Decode a node name to an object name. * * @param nodeName the name of the node * @return the name of the object * @since 1.4 */ String decodeNode(String nodeName); /** * Decode an attribute name to an object name. * * @param attributeName the name of the attribute * @return the name of the meta-data * @since 1.4 */ String decodeAttribute(String attributeName); }
从这个接口可以看出,可以实现此接口进行节点转换,完整代码如下
public class FieldNameUtil { /** * 分割驼峰字段 * @param name * @param separator * @return */ private static String separateCamelCase(String name, String separator) { StringBuilder translation = new StringBuilder(); for (int i = 0; i < name.length(); i++) { char character = name.charAt(i); if (Character.isUpperCase(character) && translation.length() != 0) { translation.append(separator); } translation.append(character); } return translation.toString(); } /** * 下划线转换为驼峰 */ public static String underscore2CamelCase(String name) { StringBuilder translation = new StringBuilder(); for (int i = 0; i < name.length(); i++) { char character = name.charAt(i); if (character == '_') continue; if (translation.length() != 0 && name.charAt(i - 1) == '_') { translation.append(Character.toUpperCase(character)); } else { translation.append(character); } } return translation.toString(); } /** * 驼峰转换为下划线 * @param name * @return */ public static String camelCase2Underscore(String name) { return separateCamelCase(name, "_").toLowerCase(); } /** * 首字母大写 * @param name * @return */ public static String upperCaseFirstLetter(String name){ StringBuilder fieldNameBuilder = new StringBuilder(); int index = 0; char firstCharacter = name.charAt(index); while (index < name.length() - 1) { if (Character.isLetter(firstCharacter)) { break; } fieldNameBuilder.append(firstCharacter); firstCharacter = name.charAt(++index); } if (index == name.length()) { return fieldNameBuilder.toString(); } if (!Character.isUpperCase(firstCharacter)) { String modifiedTarget = modifyString(Character.toUpperCase(firstCharacter), name, ++index); return fieldNameBuilder.append(modifiedTarget).toString(); } else { return name; } } /** * 首字母小写 * @param name * @return */ public static String lowerCaseFirstLetter(String name){ StringBuilder fieldNameBuilder = new StringBuilder(); int index = 0; char firstCharacter = name.charAt(index); while (index < name.length() - 1) { if (Character.isLetter(firstCharacter)) { break; } fieldNameBuilder.append(firstCharacter); firstCharacter = name.charAt(++index); } if (index == name.length()) { return fieldNameBuilder.toString(); } if (!Character.isLowerCase(firstCharacter)) { String modifiedTarget = modifyString(Character.toLowerCase(firstCharacter), name, ++index); return fieldNameBuilder.append(modifiedTarget).toString(); } else { return name; } } /** * 修改字符串 * @param firstCharacter * @param srcString * @param indexOfSubstring * @return */ private static String modifyString(char firstCharacter, String srcString, int indexOfSubstring) { return (indexOfSubstring < srcString.length()) ? firstCharacter + srcString.substring(indexOfSubstring) : String.valueOf(firstCharacter); } }
import com.thoughtworks.xstream.io.naming.NameCoder; public class UpperCaseNameCoder implements NameCoder { public String encodeNode(String name) { return name.equalsIgnoreCase("xml")?"xml":FieldNameUtil.upperCaseFirstLetter(name); } public String encodeAttribute(String name) { return name.equalsIgnoreCase("xml")?"xml":FieldNameUtil.upperCaseFirstLetter(name); } public String decodeNode(String nodeName) { return FieldNameUtil.lowerCaseFirstLetter(nodeName); } public String decodeAttribute(String attributeName) { return FieldNameUtil.lowerCaseFirstLetter(attributeName); } }
使用是,直接使用
xstream=new XStream(new DomDriver("UTF-8", new UpperCaseNameCoder())); xstream.alias("xml", textMsg); xstream.toXML(obj);
得到的结果
<xml> <ToUserName>to</ToUserName> <FromUserName>from</FromUserName> <CreateTime>1</CreateTime> <MsgType>text</MsgType> <MsgId>1</MsgId> <Content>content</Content> </xml>
总结:
根据NameCoder接口实现,我们可以把驼峰式命名修改为匹配命名规则啦,比如下划线、中线、全大写下划线等,再也不用手拼字符串啦。