Support for generic types when serializing and deserializing?

Issue #387 new
Bartłomiej Mazur created an issue

Currently SnakeYaml only support beans deserialization via:

public <T> T loadAs(String yaml, Class<T> type)

This make near impossible to write some own library for some serialization/deserialization using snakeyaml, especially if data isn't made by us and does not contain type tags in it.

It would be awesome to be able to use methods like this:

public <T> T loadAs(String yaml, Type type)
public String dumpAs(Object object, Type type) // will not include type tags for known types that can be read from generic

Just like in GSON library.

As I see you often want to see some test cases describing issue, I attached one, but ofc code will not compile, as this is request for new methods that will support that features. Test requires GSON, as I didn't want to include whole TypeToken<T> API.

Comments (11)

  1. Andrey Somov

    If you deliver the pull request with the implementation this month, we can include it in the coming release in August.

  2. Bartłomiej Mazur reporter

    Sorry, I do not have time in this month. Any chance that you will do it later? If not, then maybe I will find some time in next months, but just not in this one.

  3. Anthony Foulfoin

    Same issue for me. My yaml document root is a list of TestCase:

    - input: ["val1","val2"]
      expected: ["val3","val4"]
    - input: ["val5","val6"]
      expected: ["val7","val8"]
    

    TestCase:

    public class TestCase {
            public List<String> input;
            public List<String> expected;
     }
    

    I try to use SnakeYaml to deserialize it as a List<TestCase>:

    List<TestCase> tests = new Yaml().load(is);
    

    Unfortunately I get a List of HashMap instead, I didn't find any way in SnakeYaml to provide the List generic type.

    With jackson, when deserializing json, we can use a TypeRefence to do this, example with jackson and the same document in json:

    List<TestCase> tests = mapper.readValue(is, new TypeReference<List<TestCase>>(){});
    

    What I get is effectively a List<TestCase>!

    I found out a little hack to achieve this with SnakeYaml, I try to deserialize to a typed array first, then convert the array into a List. Since arrays are statically typed it works !

    TestCase[] testsArray = new Yaml().loadAs(is, TestCase[].class);
    List<TestCase> tests = Arrays.asList(testArray);
    

    I get a List<TestCase> like I wanted, but it looks very hacky and it would be great if SnakeYaml could natively support a mecanism like Jackson (or Gson)

    Thanks !

  4. Ling Hengqian

    The problem I found was related to this problem, but I encountered a very strange situation. The YAML I tested here can be deserialized normally in snakeyaml 1.16 version , but the generic information is lost in snakeyaml 1.19-1.30 version .

    Take the following program as an example, it works fine on snakeyaml 1.16-1.18, but in1.19-1.30 version the generic information is lost, causing ClassCastException.

    public enum ExecuteProcessConstants {
        EXECUTE_ID, EXECUTE_STATUS_START, EXECUTE_STATUS_DONE
    }
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class YamlExecuteProcessUnit {
        private String unitID;
        private ExecuteProcessConstants status;
    }
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class YamlExecuteProcessContext {
        private String executionID;
        private String schemaName;
        private String username;
        private String hostname;
        private String sql;
        private Collection<YamlExecuteProcessUnit> unitStatuses;
        private Long startTimeMillis;
    }
    
    public class JunitTest {
      @Test
      public void snakeYamlTest3() {
        String marshal = "unitStatuses: !!set\n" +
                "  ? status: EXECUTE_STATUS_DONE\n" +
                "    unitID: '159917166'\n" +
                "  : null\n";
        YamlExecuteProcessContext unmarshal = new Yaml(new Constructor(YamlExecuteProcessContext.class)).loadAs(marshal,YamlExecuteProcessContext.class);
        for (YamlExecuteProcessUnit unit : unmarshal.getUnitStatuses()) {
            System.out.println("test problem in class ---------" + unit.getClass().getName());
            if (unit.getStatus() != ExecuteProcessConstants.EXECUTE_STATUS_DONE) {
                return;
            }
        }
      }
    }
    

    The discovery of this issue is related to https://github.com/apache/shardingsphere/pull/15260 , and a separate test case is linked to snakeyaml-update-test/JunitTest.java at master · linghengqian/snakeyaml-update-test (github.com) .
    Hope some friends can tell me the reason, I didn't see enough information in the update log of snakeyaml 1.19 to support the problem.

  5. Alexander Maslov

    this is a nice one 😉 6 years nobody reported it. I have a fix for this.

    Not sure it is related to this issue, but let’s see. I need to write maybe couple of tests to see what kind of situation it fixes.
    At least no current tests are failing. And your test is also passing.

  6. Alexander Maslov

    change how we set detected type to JavaBean Collection property item

    fixes regression introduced in 1.19 when generics information is lost for JavaBean Collection property.

    refs issue #387

    → <<cset 9d7dbb869ba9>>

  7. Alexander Maslov

    change how we set detected type to JavaBean Collection property item

    fixes regression introduced in 1.19 when generics information is lost for JavaBean Collection property.

    refs issue #387

    → <<cset 6385279ecb0b>>

  8. Log in to comment