public static TypeInformation<?> readTypeInfo(String typeString) {
        final List<Token> tokens = tokenize(typeString);
        final TokenConverter converter = new TokenConverter(typeString, tokens);
        return converter.convert();
    }
@Test
    void testWriteComplexTypes() {
        testReadAndWrite("ROW<f0 DECIMAL, f1 TINYINT>", Types.ROW(Types.BIG_DEC, Types.BYTE));

        testReadAndWrite(
                "ROW<hello DECIMAL, world TINYINT>",
                Types.ROW_NAMED(new String[] {"hello", "world"}, Types.BIG_DEC, Types.BYTE));

        testReadAndWrite(
                "POJO<org.apache.flink.table.utils.TypeStringUtilsTest$TestPojo>",
                TypeExtractor.createTypeInfo(TestPojo.class));

        testReadAndWrite(
                "ANY<org.apache.flink.table.utils.TypeStringUtilsTest$TestNoPojo>",
                TypeExtractor.createTypeInfo(TestNoPojo.class));

        testReadAndWrite(
                "MAP<VARCHAR, ROW<f0 DECIMAL, f1 TINYINT>>",
                Types.MAP(Types.STRING, Types.ROW(Types.BIG_DEC, Types.BYTE)));

        testReadAndWrite(
                "MULTISET<ROW<f0 DECIMAL, f1 TINYINT>>",
                new MultisetTypeInfo<>(Types.ROW(Types.BIG_DEC, Types.BYTE)));

        testReadAndWrite("PRIMITIVE_ARRAY<TINYINT>", Types.PRIMITIVE_ARRAY(Types.BYTE));

        testReadAndWrite(
                "OBJECT_ARRAY<POJO<org.apache.flink.table.utils.TypeStringUtilsTest$TestPojo>>",
                Types.OBJECT_ARRAY(TypeExtractor.createTypeInfo(TestPojo.class)));

        // test escaping
        assertThat(TypeStringUtils.readTypeInfo("ROW<`he         \nllo` DECIMAL, world TINYINT>"))
                .isEqualTo(
                        Types.ROW_NAMED(
                                new String[] {"he         \nllo", "world"},
                                Types.BIG_DEC,
                                Types.BYTE));

        assertThat(TypeStringUtils.readTypeInfo("ROW<`he``llo` DECIMAL, world TINYINT>"))
                .isEqualTo(
                        Types.ROW_NAMED(
                                new String[] {"he`llo", "world"}, Types.BIG_DEC, Types.BYTE));

        // test backward compatibility with brackets ()
        assertThat(TypeStringUtils.readTypeInfo("ROW(`he         \nllo` DECIMAL, world TINYINT)"))
                .isEqualTo(
                        Types.ROW_NAMED(
                                new String[] {"he         \nllo", "world"},
                                Types.BIG_DEC,
                                Types.BYTE));

        // test nesting
        testReadAndWrite(
                "ROW<singleton ROW<f0 INT>, twoField ROW<`Field 1` ROW<f0 DECIMAL>, `Field``s 2` VARCHAR>>",
                Types.ROW_NAMED(
                        new String[] {"singleton", "twoField"},
                        Types.ROW(Types.INT),
                        Types.ROW_NAMED(
                                new String[] {"Field 1", "Field`s 2"},
                                Types.ROW(Types.BIG_DEC),
                                Types.STRING)));

        testWrite(
                "ROW<f0 DECIMAL, f1 TIMESTAMP, f2 TIME, f3 DATE>",
                Types.ROW_NAMED(
                        new String[] {"f0", "f1", "f2", "f3"},
                        Types.BIG_DEC,
                        Types.LOCAL_DATE_TIME,
                        Types.LOCAL_TIME,
                        Types.LOCAL_DATE));
    }