You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2017/06/21 20:00:26 UTC
svn commit: r1799501 - in /commons/proper/configuration/trunk: ./
src/main/java/org/apache/commons/configuration2/
src/test/java/org/apache/commons/configuration2/ src/test/resources/
Author: oheger
Date: Wed Jun 21 20:00:25 2017
New Revision: 1799501
URL: http://svn.apache.org/viewvc?rev=1799501&view=rev
Log:
[CONFIGURATION-258] Support for JSON and YAML configurations.
Thanks to The Alchemist (kap4020 at gmail dot com) for the patch.
Added:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractMapBasedConfiguration.java
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/JSONConfiguration.java
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
commons/proper/configuration/trunk/src/test/resources/test.json
commons/proper/configuration/trunk/src/test/resources/test.yaml
Modified:
commons/proper/configuration/trunk/pom.xml
Modified: commons/proper/configuration/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/pom.xml?rev=1799501&r1=1799500&r2=1799501&view=diff
==============================================================================
--- commons/proper/configuration/trunk/pom.xml (original)
+++ commons/proper/configuration/trunk/pom.xml Wed Jun 21 20:00:25 2017
@@ -469,6 +469,22 @@
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
+
+
+ <dependency>
+ <groupId>org.yaml</groupId>
+ <artifactId>snakeyaml</artifactId>
+ <version>1.18</version>
+ <optional>true</optional>
+ </dependency>
+
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.5.3</version>
+ <optional>true</optional>
+ </dependency>
</dependencies>
<properties>
Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractMapBasedConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractMapBasedConfiguration.java?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractMapBasedConfiguration.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/AbstractMapBasedConfiguration.java Wed Jun 21 20:00:25 2017
@@ -0,0 +1,92 @@
+package org.apache.commons.configuration2;
+
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.ConfigurationLogger;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author The-Alchemist
+ */
+public class AbstractMapBasedConfiguration extends BaseHierarchicalConfiguration {
+
+ public AbstractMapBasedConfiguration() {
+ super();
+
+ initLogger(new ConfigurationLogger(getClass()));
+ }
+
+ protected void load(Map<String, Object> map) {
+ ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder();
+ ImmutableNode top = constructHierarchy(rootBuilder, map);
+ getNodeModel().setRootNode(top);
+ }
+
+ /**
+ * Constructs a YAML map, i.e. String -> Object from a given
+ * configuration node.
+ *
+ * @param node The configuration node to create a map from.
+ * @return A Map that contains the configuration node information.
+ */
+ protected Map<String, Object> constructMap(ImmutableNode node)
+ {
+ Map<String, Object> map = new HashMap<String, Object>(node.getChildren().size());
+ for (ImmutableNode cNode : node.getChildren())
+ {
+ if (cNode.getChildren().isEmpty())
+ {
+ map.put(cNode.getNodeName(), cNode.getValue());
+ }
+ else
+ {
+ map.put(cNode.getNodeName(), constructMap(cNode));
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Constructs the internal configuration nodes hierarchy.
+ * @param parent The configuration node that is the root of the current configuration section.
+ * @param map The map with the yaml configurations nodes, i.e. String -> Object.
+ */
+ protected ImmutableNode constructHierarchy(ImmutableNode.Builder parent, Map<String, Object> map)
+ {
+ for (Map.Entry<String, Object> entry : map.entrySet())
+ {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof Map)
+ {
+ ImmutableNode.Builder subtree = new ImmutableNode.Builder()
+ .name(key);
+ ImmutableNode children = constructHierarchy(subtree, (Map) value);
+ parent.addChild(children);
+ }
+ else
+ {
+ ImmutableNode leaf = new ImmutableNode.Builder()
+ .name(key)
+ .value(value)
+ .create();
+ parent.addChild(leaf);
+ }
+ }
+ return parent.create();
+ }
+
+
+ static void rethrowException(Exception e) throws ConfigurationException {
+ if(e instanceof ClassCastException)
+ {
+ throw new ConfigurationException("Error parsing", e);
+ }
+ else
+ {
+ throw new ConfigurationException("Unable to load the configuration", e);
+ }
+ }
+}
Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/JSONConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/JSONConfiguration.java?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/JSONConfiguration.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/JSONConfiguration.java Wed Jun 21 20:00:25 2017
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.MapType;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.InputStreamSupport;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration class that is able to parse JSON
+ * documents.
+ * </p>
+ *
+ * @author The-Alchemist
+ *
+ * @since commons-configuration2 2.2.?
+ * @version $$
+ */
+
+public class JSONConfiguration extends AbstractMapBasedConfiguration implements
+ FileBasedConfiguration, InputStreamSupport
+{
+
+ private final ObjectMapper mapper = new ObjectMapper();
+ private final MapType type = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class);
+
+
+ /**
+ * Creates a new instance of {@code YAMLConfiguration}.
+ */
+ public JSONConfiguration() {
+ super();
+ }
+
+ @Override
+ public void read(Reader in) throws ConfigurationException
+ {
+ try
+ {
+ Map<String, Object> map = mapper.readValue(in, this.type);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+ private String readFully(Reader in) {
+ try (BufferedReader r = new BufferedReader(in)) {
+ String str = null;
+ StringBuilder sb = new StringBuilder();
+ while((str =r.readLine())!=null)
+ {
+ sb.append(str);
+ }
+ return sb.toString();
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ @Override
+ public void write(Writer out) throws ConfigurationException, IOException
+ {
+ this.mapper.writer().writeValue(out, constructMap(this.getNodeModel().getNodeHandler().getRootNode()));
+ }
+
+
+ /**
+ * Loads the configuration from the given input stream.
+ *
+ * @param in the input stream
+ * @throws ConfigurationException if an error occurs
+ */
+ @Override
+ public void read(InputStream in) throws ConfigurationException
+ {
+ try
+ {
+ Map<String, Object> map = mapper.readValue(in, this.type);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+
+
+}
\ No newline at end of file
Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java Wed Jun 21 20:00:25 2017
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration2;
+
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.ConfigurationLogger;
+import org.apache.commons.configuration2.io.InputStreamSupport;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration class that is able to parse YAML
+ * documents.
+ * </p>
+ *
+ * @author The-Alchemist
+ *
+ * @since commons-configuration2 2.2.?
+ * @version $$
+ */
+
+public class YAMLConfiguration extends AbstractMapBasedConfiguration implements
+ FileBasedConfiguration, InputStreamSupport
+{
+
+ /**
+ * Creates a new instance of {@code YAMLConfiguration}.
+ */
+ public YAMLConfiguration()
+ {
+ super();
+ }
+
+
+ @Override
+ public void read(Reader in) throws ConfigurationException
+ {
+ try
+ {
+ Yaml yaml = new Yaml();
+ Map<String, Object> map = (Map) yaml.load(in);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+
+ public void read(Reader in, LoaderOptions options) throws ConfigurationException
+ {
+ try
+ {
+ Yaml yaml = new Yaml(options);
+ Map<String, Object> map = (Map) yaml.load(in);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+ @Override
+ public void write(Writer out) throws ConfigurationException, IOException
+ {
+ DumperOptions options = new DumperOptions();
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ dump(out, options);
+ }
+
+ public void dump(Writer out, DumperOptions options) throws ConfigurationException, IOException
+ {
+ Yaml yaml = new Yaml(options);
+ yaml.dump(constructMap(getNodeModel().getNodeHandler().getRootNode()), out);
+ }
+
+
+ /**
+ * Loads the configuration from the given input stream.
+ *
+ * @param in the input stream
+ * @throws ConfigurationException if an error occurs
+ */
+ @Override
+ public void read(InputStream in) throws ConfigurationException
+ {
+ try
+ {
+ Yaml yaml = new Yaml();
+ Map<String, Object> map = (Map) yaml.load(in);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+ public void read(InputStream in, LoaderOptions options) throws ConfigurationException
+ {
+ try
+ {
+ Yaml yaml = new Yaml(options);
+ Map<String, Object> map = (Map) yaml.load(in);
+ load(map);
+ }
+ catch (Exception e)
+ {
+ rethrowException(e);
+ }
+ }
+
+
+}
\ No newline at end of file
Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestJSONConfiguration.java Wed Jun 21 20:00:25 2017
@@ -0,0 +1,123 @@
+package org.apache.commons.configuration2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.MapType;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit test for {@link JSONConfiguration}
+ *
+ * Not ideal: it uses the Jackson JSON parser just like {@link JSONConfiguration} itself
+ * @author The-Alchemist
+ */
+public class TestJSONConfiguration {
+ /** The files that we test with. */
+ private String testJson = ConfigurationAssert.getTestFile("test.json").getAbsolutePath();
+ private File testSaveConf = ConfigurationAssert.getOutFile("testsave.json");
+
+ private JSONConfiguration jsonConfiguration;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ jsonConfiguration = new JSONConfiguration();
+ jsonConfiguration.read(new FileReader(testJson));
+ removeTestFile();
+ }
+
+ @Test
+ public void testGetProperty_simple()
+ {
+ assertEquals("value1", jsonConfiguration.getProperty("key1"));
+ }
+
+ @Test
+ public void testGetProperty_nested()
+ {
+ assertEquals("value23", jsonConfiguration.getProperty("key2.key3"));
+ }
+
+ @Test
+ public void testGetProperty_nested_with_list()
+ {
+ assertEquals(Arrays.asList("col1", "col2"), jsonConfiguration.getProperty("key4.key5"));
+ }
+
+ @Test
+ public void testGetProperty_subset()
+ {
+ Configuration subset = jsonConfiguration.subset("key4");
+ assertEquals(Arrays.asList("col1", "col2"), subset.getProperty("key5"));
+ }
+
+
+ @Test
+ public void testGetProperty_very_nested_properties()
+ {
+ Object property = jsonConfiguration.getProperty("very.nested.properties");
+ assertEquals(Arrays.asList("nested1", "nested2", "nested3"), property);
+ }
+
+ @Test
+ public void testGetProperty_integer()
+ {
+ Object property = jsonConfiguration.getProperty("int1");
+ assertTrue("property should be an Integer", property instanceof Integer);
+ assertEquals(37, property);
+ }
+
+ @Test
+ public void testSave() throws IOException, ConfigurationException {
+ // save the Configuration as a String...
+ StringWriter sw = new StringWriter();
+ jsonConfiguration.write(sw);
+ String output = sw.toString();
+
+ // ..and then try parsing it back
+ ObjectMapper mapper = new ObjectMapper();
+ MapType type = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class);
+ Map<String, Object> parsed = mapper.readValue(output, type);
+ assertEquals(6, parsed.entrySet().size());
+ assertEquals("value1", parsed.get("key1"));
+
+ Map key2 = (Map) parsed.get("key2");
+ assertEquals("value23", key2.get("key3"));
+
+ List<String> key5 = (List<String>) ((Map) parsed.get("key4")).get("key5");
+ assertEquals(2, key5.size());
+ assertEquals("col1", key5.get(0));
+ assertEquals("col2", key5.get(1));
+ }
+
+ @Test
+ public void testGetProperty_dictionary()
+ {
+ assertEquals("Martin D'vloper", jsonConfiguration.getProperty("martin.name"));
+ assertEquals("Developer", jsonConfiguration.getProperty("martin.job"));
+ assertEquals("Elite", jsonConfiguration.getProperty("martin.skill"));
+ }
+
+ /**
+ * Removes the test output file if it exists.
+ */
+ private void removeTestFile()
+ {
+ if (testSaveConf.exists())
+ {
+ assertTrue(testSaveConf.delete());
+ }
+ }
+
+}
\ No newline at end of file
Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java Wed Jun 21 20:00:25 2017
@@ -0,0 +1,123 @@
+package org.apache.commons.configuration2;
+
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.junit.Before;
+import org.junit.Test;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Unit test for {@link YAMLConfiguration}
+ *
+ * @author The-Alchemist
+ */
+public class TestYAMLConfiguration
+{
+ /** The files that we test with. */
+ private String testYaml = ConfigurationAssert.getTestFile("test.yaml").getAbsolutePath();
+ private File testSaveConf = ConfigurationAssert.getOutFile("testsave.yaml");
+
+ private YAMLConfiguration yamlConfiguration;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ yamlConfiguration = new YAMLConfiguration();
+ yamlConfiguration.read(new FileReader(testYaml));
+ removeTestFile();
+ }
+
+ @Test
+ public void testGetProperty_simple()
+ {
+ assertEquals("value1", yamlConfiguration.getProperty("key1"));
+ }
+
+ @Test
+ public void testGetProperty_nested()
+ {
+ assertEquals("value23", yamlConfiguration.getProperty("key2.key3"));
+ }
+
+ @Test
+ public void testGetProperty_nested_with_list()
+ {
+ assertEquals(Arrays.asList("col1", "col2"), yamlConfiguration.getProperty("key4.key5"));
+ }
+
+ @Test
+ public void testGetProperty_subset()
+ {
+ Configuration subset = yamlConfiguration.subset("key4");
+ assertEquals(Arrays.asList("col1", "col2"), subset.getProperty("key5"));
+ }
+
+
+ @Test
+ public void testGetProperty_very_nested_properties()
+ {
+ Object property = yamlConfiguration.getProperty("very.nested.properties");
+ assertEquals(Arrays.asList("nested1", "nested2", "nested3"), property);
+ }
+
+ @Test
+ public void testGetProperty_integer()
+ {
+ Object property = yamlConfiguration.getProperty("int1");
+ assertTrue("property should be an Integer", property instanceof Integer);
+ assertEquals(37, property);
+ }
+
+
+ @Test
+ public void testSave() throws IOException, ConfigurationException
+ {
+ // save the YAMLConfiguration as a String...
+ StringWriter sw = new StringWriter();
+ yamlConfiguration.write(sw);
+ String output = sw.toString();
+
+ // ..and then try parsing it back as using SnakeYAML
+ Map parsed = new Yaml().loadAs(output, Map.class);
+ assertEquals(6, parsed.entrySet().size());
+ assertEquals("value1", parsed.get("key1"));
+
+ Map key2 = (Map) parsed.get("key2");
+ assertEquals("value23", key2.get("key3"));
+
+ List<String> key5 = (List<String>) ((Map) parsed.get("key4")).get("key5");
+ assertEquals(2, key5.size());
+ assertEquals("col1", key5.get(0));
+ assertEquals("col2", key5.get(1));
+ }
+
+ @Test
+ public void testGetProperty_dictionary()
+ {
+ assertEquals("Martin D'vloper", yamlConfiguration.getProperty("martin.name"));
+ assertEquals("Developer", yamlConfiguration.getProperty("martin.job"));
+ assertEquals("Elite", yamlConfiguration.getProperty("martin.skill"));
+ }
+
+ /**
+ * Removes the test output file if it exists.
+ */
+ private void removeTestFile()
+ {
+ if (testSaveConf.exists())
+ {
+ assertTrue(testSaveConf.delete());
+ }
+ }
+}
Added: commons/proper/configuration/trunk/src/test/resources/test.json
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/resources/test.json?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/resources/test.json (added)
+++ commons/proper/configuration/trunk/src/test/resources/test.json Wed Jun 21 20:00:25 2017
@@ -0,0 +1,27 @@
+{
+ "key1": "value1",
+ "key2": {
+ "key3": "value23"
+ },
+ "key4": {
+ "key5": [
+ "col1",
+ "col2"
+ ]
+ },
+ "very": {
+ "nested": {
+ "properties": [
+ "nested1",
+ "nested2",
+ "nested3"
+ ]
+ }
+ },
+ "int1": 37,
+ "martin": {
+ "name": "Martin D'vloper",
+ "job": "Developer",
+ "skill": "Elite"
+ }
+}
Added: commons/proper/configuration/trunk/src/test/resources/test.yaml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/resources/test.yaml?rev=1799501&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/resources/test.yaml (added)
+++ commons/proper/configuration/trunk/src/test/resources/test.yaml Wed Jun 21 20:00:25 2017
@@ -0,0 +1,26 @@
+---
+key1:
+ value1
+
+key2:
+ key3: value23
+
+key4:
+ key5:
+ - col1
+ - col2
+
+very:
+ nested:
+ properties:
+ - nested1
+ - nested2
+ - nested3
+
+int1:
+ 37
+
+martin:
+ name: Martin D'vloper
+ job: Developer
+ skill: Elite
\ No newline at end of file