使用 SnakeYAML 保持标签顺序
Posted
技术标签:
【中文标题】使用 SnakeYAML 保持标签顺序【英文标题】:Keep tags order using SnakeYAML 【发布时间】:2015-10-10 14:37:59 【问题描述】:我正在尝试将 yaml 文件翻译成 json,但翻译会重新排序标签... 例如,YAML 来源:
zzzz:
b: 456
a: dfff
aa:
s10: "dddz"
s3: eeee
bbb:
- b1
- a2
snakeYAML 产生:
"aa":
"s3": "eeee",
"s10":"dddz"
,
"bbb":[
"b1",
"a2"
],
"zzzz":
"a": "dfff",
"b":456
【问题讨论】:
在 YAML 中,地图是无序的,这就是你的翻译重新排序标签的原因。为了保留标签排序,您需要使用低级 YAML 解析,它允许您重新实现令牌的处理。可能这会有所帮助:code.google.com/p/snakeyaml/wiki/Documentation#Low_Level_API. SnakeYAML 的代码和文档不断更新,请参阅the Bitbucket site for the Low Level API。 【参考方案1】:在您的代码中创建以下类,这是来自 SnakeYAML 源的调整版本,它使用 LinkedHashMap
和 LinkedHashSet
保持插入顺序,而不是自动排序的 TreeMap
和 TreeSet
。
import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.*;
import org.yaml.snakeyaml.util.PlatformFeatureDetector;
public class CustomPropertyUtils extends PropertyUtils
private final Map<Class<?>, Map<String, Property>> propertiesCache = new HashMap<Class<?>, Map<String, Property>>();
private final Map<Class<?>, Set<Property>> readableProperties = new HashMap<Class<?>, Set<Property>>();
private BeanAccess beanAccess = BeanAccess.DEFAULT;
private boolean allowReadOnlyProperties = false;
private boolean skipMissingProperties = false;
private PlatformFeatureDetector platformFeatureDetector;
public CustomPropertyUtils()
this(new PlatformFeatureDetector());
CustomPropertyUtils(PlatformFeatureDetector platformFeatureDetector)
this.platformFeatureDetector = platformFeatureDetector;
/*
* android lacks much of java.beans (including the Introspector class, used here), because java.beans classes tend to rely on java.awt, which isn't
* supported in the Android SDK. That means we have to fall back on FIELD access only when SnakeYAML is running on the Android Runtime.
*/
if (platformFeatureDetector.isRunningOnAndroid())
beanAccess = BeanAccess.FIELD;
protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess)
if (propertiesCache.containsKey(type))
return propertiesCache.get(type);
Map<String, Property> properties = new LinkedHashMap<String, Property>();
boolean inaccessableFieldsExist = false;
switch (bAccess)
case FIELD:
for (Class<?> c = type; c != null; c = c.getSuperclass())
for (Field field : c.getDeclaredFields())
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)
&& !properties.containsKey(field.getName()))
properties.put(field.getName(), new FieldProperty(field));
break;
default:
// add JavaBean properties
try
for (PropertyDescriptor property : Introspector.getBeanInfo(type)
.getPropertyDescriptors())
Method readMethod = property.getReadMethod();
if ((readMethod == null || !readMethod.getName().equals("getClass"))
&& !isTransient(property))
properties.put(property.getName(), new MethodProperty(property));
catch (IntrospectionException e)
throw new YAMLException(e);
// add public fields
for (Class<?> c = type; c != null; c = c.getSuperclass())
for (Field field : c.getDeclaredFields())
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers))
if (Modifier.isPublic(modifiers))
properties.put(field.getName(), new FieldProperty(field));
else
inaccessableFieldsExist = true;
break;
if (properties.isEmpty() && inaccessableFieldsExist)
throw new YAMLException("No JavaBean properties found in " + type.getName());
System.out.println(properties);
propertiesCache.put(type, properties);
return properties;
private static final String TRANSIENT = "transient";
private boolean isTransient(FeatureDescriptor fd)
return Boolean.TRUE.equals(fd.getValue(TRANSIENT));
public Set<Property> getProperties(Class<? extends Object> type)
return getProperties(type, beanAccess);
public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess)
if (readableProperties.containsKey(type))
return readableProperties.get(type);
Set<Property> properties = createPropertySet(type, bAccess);
readableProperties.put(type, properties);
return properties;
protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)
Set<Property> properties = new LinkedHashSet<>();
Collection<Property> props = getPropertiesMap(type, bAccess).values();
for (Property property : props)
if (property.isReadable() && (allowReadOnlyProperties || property.isWritable()))
properties.add(property);
return properties;
public Property getProperty(Class<? extends Object> type, String name)
return getProperty(type, name, beanAccess);
public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess)
Map<String, Property> properties = getPropertiesMap(type, bAccess);
Property property = properties.get(name);
if (property == null && skipMissingProperties)
property = new MissingProperty(name);
if (property == null)
throw new YAMLException(
"Unable to find property '" + name + "' on class: " + type.getName());
return property;
public void setBeanAccess(BeanAccess beanAccess)
if (platformFeatureDetector.isRunningOnAndroid() && beanAccess != BeanAccess.FIELD)
throw new IllegalArgumentException(
"JVM is Android - only BeanAccess.FIELD is available");
if (this.beanAccess != beanAccess)
this.beanAccess = beanAccess;
propertiesCache.clear();
readableProperties.clear();
public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties)
if (this.allowReadOnlyProperties != allowReadOnlyProperties)
this.allowReadOnlyProperties = allowReadOnlyProperties;
readableProperties.clear();
public boolean isAllowReadOnlyProperties()
return allowReadOnlyProperties;
/**
* Skip properties that are missing during deserialization of YAML to a Java
* object. The default is false.
*
* @param skipMissingProperties
* true if missing properties should be skipped, false otherwise.
*/
public void setSkipMissingProperties(boolean skipMissingProperties)
if (this.skipMissingProperties != skipMissingProperties)
this.skipMissingProperties = skipMissingProperties;
readableProperties.clear();
public boolean isSkipMissingProperties()
return skipMissingProperties;
然后,像这样创建您的 Yaml
实例:
DumperOptions options = new DumperOptions();
CustomPropertyUtils customPropertyUtils = new CustomPropertyUtils();
Representer customRepresenter = new Representer();
customRepresenter.setPropertyUtils(customPropertyUtils);
Yaml yaml = new Yaml(customRepresenter, options);
利润!
【讨论】:
哦 crikey 8[ 8[【参考方案2】:属性顺序的保持取决于 java 实现,不能保证。
为了控制 yaml 生成,您需要实现 CustomRepresenter
覆盖 getProperties
,请参见下面的示例:
package io.github.rockitconsulting.test.rockitizer.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.representer.Representer;
/**
* Custom implementation of @link Representer and @link Comparator
* to keep the needed order of javabean properties of model classes,
* thus generating the understandable yaml
*
*/
public class ConfigurationModelRepresenter extends Representer
public ConfigurationModelRepresenter()
super();
public ConfigurationModelRepresenter(DumperOptions options)
super(options);
protected Set<Property> getProperties(Class<? extends Object> type)
Set<Property> propertySet;
if (typeDefinitions.containsKey(type))
propertySet = typeDefinitions.get(type).getProperties();
propertySet = getPropertyUtils().getProperties(type);
List<Property> propsList = new ArrayList<>(propertySet);
Collections.sort(propsList, new BeanPropertyComparator());
return new LinkedHashSet<>(propsList);
class BeanPropertyComparator implements Comparator<Property>
public int compare(Property p1, Property p2)
if (p1.getType().getCanonicalName().contains("util") && !p2.getType().getCanonicalName().contains("util"))
return 1;
else if(p2.getName().endsWith("Name")|| p2.getName().equalsIgnoreCase("name"))
return 1;
else
return -1;
// returning 0 would merge keys
下面的sn -p展示了新创建的类生成yaml结构的用法:
DumperOptions options = new DumperOptions();
ConfigurationModelRepresenter customRepresenter = new ConfigurationModelRepresenter();
Yaml yaml = new Yaml(customRepresenter, options);
StringWriter writer = new StringWriter();
yaml.dump(suite, writer);
FileWriter fw = new FileWriter(rootPathTestSrc + "config_gen.yml", false);
fw.write(writer.toString());
fw.close();
与上面建议的方法相比,这种方法更简洁。
【讨论】:
以上是关于使用 SnakeYAML 保持标签顺序的主要内容,如果未能解决你的问题,请参考以下文章
使用 scikit learn 进行标签编码时如何保持自然顺序