如何在 Salesforce Apex 中解析 YAML 对象

How to parse YAML object in Salesforce Apex

我从 Apex 代码中的 REST 服务响应中得到一个 YAML 字符串,我寻找一些类似的方法来解析它并将其转换为 Salesforce 对象的集合。

例如,我得到这样的 YAML 作为字符串:

---
item:
  - 
    title: A Grief Observed
    author: C. S. Lewis
    author_by_last: Lewis, C. S.
    isbn: "0060652381"
    publisher: ZOND
    on_hand: "5"
    in_pub_date: 2001-01-01
  - 
    title: "Grief Sanctified: From Sorrow to Eternal Hope: Including Richard Baxter's Timeles"
    author: J. I. Packer
    author_by_last: Packer, J. I.
    isbn: "1581344406"
    publisher: CROSS
    on_hand: "5"
    in_pub_date: 2002-09-01

我想将其转换为 Book 对象列表。当我使用 JSON 时,我使用了 JSON class,它提供了反序列化功能,我想知道,Salesforce 是否为 YAML 处理提供了类似的东西?

注意:这是 copy/pasted 我在 same question on the Salesforce SE site 上给出的答案。

不,在撰写本文时 (API v41.0),Salesforce 没有任何内置功能来创建或解析 YAML。这意味着您需要构建自己的(或将现有语言从另一种语言改编成 Apex,例如 Java)。

如果您不是在寻找通用解析器(可以处理任何有效 YAML 的东西)并且您不希望您接收的数据格式发生变化,请编写一个特定于域的解析器不应该做太多工作。

当我发现自己在使用 XML 时,我喜欢将传入的模式分解为单独的顶点 类 并在每个顶点中构建解析方法。这样做可以使解析器易于管理。 YAML 也可以采用类似的方法。

// Yes, the need for this BookCollection object is debatable (it's mainly just storing
//   a List).
// Encapsulating the parsing makes it worth being made into a class (in my mind).
public class BookCollection{
    // The class variables for each level mimic the data stored on each level
    //   of the schema of your incoming data.
    // This will become more apparent later.
    public List<Book> item;

    public BookCollection(String input){
        item = new List<Book>();

        // At this level, all we're concerned about is finding the individual books.
        // Once we find a book, we pass it down to the next level of parsing (and
        //   add the result to our list)
        // YAML uses whitespace to denote structure, so we need to take that into
        //   account when splitting.
        // The regex here looks for two spaces, a hyphen, one space, a newline.
        // Everything after that (up to the next '  - \n' or EOF) is book data.

        // String.split() will return 'item:' as the first part.
        // That isn't part of the data for a book, so we'll want to remove that.
        List<String> bookStringsList = input.split('  - \n');
        bookStringsList.remove(0);

        for(String bookString :bookStringsList){
            Book currentBook = new Book(bookString);
            item.add(currentBook);
        }
    }
}

public class Book{
    // Now it should be more apparent that we're mimicking the structure of the
    //   incoming data.
    String title;
    String author;
    String author_by_last;
    String isbn;
    String publisher;
    Integer on_hand;
    Date in_pub_date;

    public Book(String input){
        // On this level of parsing, we have actual data to work with.
        // Our job here is to find all of the key:value pairs, and cast them to
        //   their appropriate types.
        for(String keyAndValue :input.split('    \n')){
            String key, value;
            List<String> kvSplit = keyAndValue.split(':');
            key = kvSplit[0];
            // Double quotes are likely to mess things up, so remove them.
            value = kvSplit[1].replace('"', '');

            // There's probably a more elegant way to handle this than a big 'ol
            //   if/else if chain...but this'll work
            if(key == 'title'){
                this.title = value;
            } else if(key == 'author'){
                this.author = value;
            } else if(key == 'author_by_last'){
                this.author_by_last = value;
            } else if(key == 'isbn'){
                this.isbn = value;
            } else if(key == 'publisher'){
                this.publisher = value;
            } else if(key == 'on_hand'){
                // String -> Integer is pretty easy, we can use Integer.valueOf()
                this.on_hand = Integer.valueOf(value);
            } else if(key == 'in_pub_date'){
                // Dates are a bit tricky.
                // Salesforce wants them in YYYY-MM-DDThh:mm:ssZ format, or the format
                //   used in your locale (for parse() or valueOf()).
                // Given our data, it's easiest to simply generate a new date instance
                List<String> dateParts = value.split('-');

                this.in_pub_date = Date.newInstance(dateParts[0], dateParts[1], dateParts[2]);
            }
        }
    }
}

使用这些 类 非常简单。但是,它确实需要一些额外的设置。

// Your YAML, from some source
String myYAML = blackBox.getData();

// Break up your YAML's documents before attempting to parse each one.
List<String> documents = myYAML.split('---\n');

// The first result string will likely always be empty, so we can remove that.
documents.remove(0);

// This is the simple case where we know we're only dealing with a single document.
// If you had multiple documents in a single YAML string, you would (hopefully)
//   be able to tell which type of document you were working with (and you'd
//   need additional logic to determine which parser class to send the data to).
BookCollection myBooks = new BookCollection(documents[0]);