Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep tags order using SnakeYAML

I'm trying to translate yaml files to json, but the translation re-orders the tags... Ex, YAML source:

zzzz:
  b: 456
  a: dfff
aa:
  s10: "dddz"
  s3: eeee
bbb:
 - b1
 - a2

snakeYAML produces:

{
  "aa": {
    "s3": "eeee",
    "s10":"dddz"
  },
  "bbb":[
    "b1",
    "a2"
  ],
  "zzzz": {
    "a": "dfff",
    "b":456
  }
}
like image 794
Daniel Avatar asked Jul 21 '15 08:07

Daniel


People also ask

What is the use of SnakeYAML?

SnakeYAML allows you to read a YAML file into a simple Map object or parse the file and convert it into a custom Java object. Depending on your requirements you can decide in which format you want to read your YAML files.

Is SnakeYAML thread safe?

Since the implementation is not thread safe, different threads must have their own Yaml instance.

How to write Yaml File in java?

Write Object to YAML Data is in java POJO object, Create an object mapper with yaml implementation class - YAMLFactory writeValue method creates a yaml file using a POJO object.

What is Yaml File java?

yml) File: YAML is a configuration language. Languages like Python, Ruby, Java heavily use it for configuring the various properties while developing the applications. If you have ever used Elastic Search instance and MongoDB database, both of these applications use YAML(. yml) as their default configuration format.


1 Answers

Create following class in your code, this is a tweaked version from the SnakeYAML source that uses LinkedHashMap and LinkedHashSet that keep the insertion order instead of TreeMap and TreeSet which auto-sort them.

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;
    }
}

Then, create your Yaml instance like this:

    DumperOptions options = new DumperOptions();
    CustomPropertyUtils customPropertyUtils = new CustomPropertyUtils();
    Representer customRepresenter = new Representer();
    customRepresenter.setPropertyUtils(customPropertyUtils);
    Yaml yaml = new Yaml(customRepresenter, options);

Profit!

like image 89
bekce Avatar answered Nov 10 '22 08:11

bekce