15

有人可以指导如何编写一个接收 JSON 数据的类,并尝试将其解析为一个简单的缓冲列表,我们可以从中读取数据吗?

前任。JSON

{ name: 'John', age: 56 } 

..将被解析成键值对表

name John
age  56

如何编写有助于创建更快更简单的解析方法?

请不要建议任何现有的图书馆。提供解析 JSON 的概念。

4

4 回答 4

29

这个答案假设您真的想编写一个解析器并准备付出所需的努力。

您必须从 JSON 的正式规范开始。我找到了http://www.ietf.org/rfc/rfc4627.txt。这精确地定义了语言。您必须实现规范中的所有内容并为其编写测试。您的解析器必须满足不正确的 JSON(如您的)并抛出异常。

如果您想编写解析器,请停下来思考,然后不要。让它正常工作需要做很多工作。无论你做什么,都要做好它的工作——不完整的解析器是一种威胁,永远不应该分发。

您必须编写符合要求的代码。以下是规范中的一些短语。如果您不理解它们,则必须仔细研究并确保您理解:

“JSON 文本应以 Unicode 编码。默认编码为 UTF-8。”

“JSON 解析器必须接受所有符合 JSON 语法的文本。”

“编码注意事项:如果是 UTF-8,则为 8 位;如果是 UTF-16 或 UTF-32,则为二进制

  JSON may be represented using UTF-8, UTF-16, or UTF-32.  When JSON
  is written in UTF-8, JSON is 8bit compatible.  When JSON is
  written in UTF-16 or UTF-32, the binary content-transfer-encoding
  must be used.

"

"任何字符都可以被转义。如果该字符在基本
多语言平面(U+0000 到 U+FFFF),那么它可以
表示为一个六字符序列:一个反斜线,后跟
小写字母 u,然后由对字符的代码点进行编码的四个十六进制数字
。十六进制字母 A 到
F 可以是大写或小写。因此,例如,
仅包含一个反向斜线字符的字符串可以表示为
“\u005C”。

如果您理解了这些并且仍然想编写一个解析器,那么请查看其他一些解析器,看看它们是否有一致性测试。为您自己的应用程序借用这些。

如果您仍然热衷于,您应该强烈考虑使用解析器生成器。例如 JAVACC、CUP 和我的首选工具 ANTLR。ANTLR 非常强大,但可能很难开始。另请参阅我现在推荐的 Parboiled 的建议。JSON 相对简单,这将是一个有用的练习。大多数解析器生成器生成一个完整的解析器,它可以创建可执行代码或生成 JSON 的解析树。

如果允许您查看的话,在http://www.antlr.org/wiki/display/ANTLR3/JSON+Interpreter有一个使用 ANTLR 的 JSON 解析器生成器。我还刚刚发现了一个用于 JSON 的 Parboiled 解析器生成器。如果您编写解析器的主要原因是学习如何去做,那么这可能是一个很好的起点。

如果不允许(或不想)使用解析器生成器,那么您将不得不创建自己的解析器。这通常分为两部分:

词法分析器/标记器。这识别了语言规范中定义的基本原语。在这种情况下,它必须识别大括号、引号等。它可能还会构建数字的表示。

AbstractSyntaxTreehttp://en.wikipedia.org/wiki/Abstract_syntax_tree,AST生成器。在这里,您编写代码来组装表示 JSON 抽象的树(例如,已丢弃空格和花括号)。

当您拥有 AST 时,应该很容易迭代节点并创建所需的输出。

但是编写解析器生成器,即使是像 JSON 这样的简单语言,也是一项繁重的工作。

于 2013-06-12T11:06:03.230 回答
7

如果你的“JSON”真的是这样,你应该先拿一根棒球棒去敲它的生产者的头。严重地。

如果您真的坚持编写自己的类(为什么?),例如,您可以使用这样的接口:

public interface MyParser
{
    boolean parse()
        throws MyParsingException;
    MyParser next();
}

然后实现将 aCharBuffer作为参数和一个地图构建器类;并解析你会做:

final CharBuffer buf = CharBuffer.wrap(yourSource);
final MyMapBuilder builder = new MyMapBuilder();

MyParser parser = new OpenBracketParser(buf, builder);

while (parser.parse())
    parser = parser.next();

// result is builer.build()

这只是一个例子……

第二种解决方案,您想使用现有的解析工具;在这种情况下,看看Parboiled。因为你用纯 Java 编写语法,所以比 antlr、jflex 或其他更容易使用。

最后,如果您认为足够了,并决定使用 JSON 库(您真的应该这样做),请使用Jackson,它甚至可以读取格式错误的 JSON:

public static void main(final String... args)
    throws IOException
{
    final ObjectMapper mapper = new ObjectMapper()
        .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
        .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

    final JsonNode node = mapper.readTree("{name: 'John'}");
    System.out.println(node); // {"name":"John"}
}
于 2013-06-12T11:02:22.810 回答
6

我以前写过一篇。脚步:

  1. 取一个表示 JSON 文本的字符串。

  2. 创建一个 JsonToken 类。我叫我的 JToken。

  3. 遍历步骤#1 中的整个文本并解析出 JToken(s)。

  4. 递归地分组和嵌套您的 JToken(s)。

  5. 尝试保持简单和统一。所有 JToken 节点都有一个可以有 0 个或多个子节点的子数组。如果节点是数组,则标记为数组。如果 is 是 OBJECT 或 ARRAY,则子数组用于节点的子节点。唯一改变的是它被标记为什么。还将所有值保留为字符串类型。这样,您只需要节点上的一个名为“值”的成员,在完成所有艰苦工作后,它可以被解释为正确的数据类型。

  6. 使用防御性编码和单元测试。为解析器的所有组件编写测试。最好多花 3 个小时以偏执的方式编写代码,假设你每秒都在犯错误,而不是花 3 个小时来寻找错误。代码偏执狂,你很少会在调试时花时间感到沮丧。

示例代码:当我在 code-eval.com 上做一个简单的(具有讽刺意味的)挑战时。有一个 json 菜单解析挑战。我认为使用任何内置函数都是作弊,因为对我来说,代码挑战的全部意义在于测试你的算法问题解决能力。挑战就在这里:https ://www.codeeval.com/open_challenges/102/

我的代码,通过了这个挑战,使用了一个在 javascript 中从头构建的解析器:

CODE: https://pastebin.com/BReK9iij
Was not able to post it on stack-overflow because it is too much code.
Put it in a non-expiring paste-bin post.

注意:此代码可以使用一些改进。其中一些效率非常低,并且不适用于 Unicode。

除非您以某种非标准方式解释 JSON,否则我不建议您编写自己的 JSON 解析器。

例如:我目前正在使用 JSONedit 来组织基于文本的冒险的分支。我只使用 JSON 文件格式,因为它很紧凑,而且查看器允许我扩展和收缩项目。GOLang 附带的标准解析器不会按照我希望的方式解释信息,所以我正在编写自己的解析器。

于 2017-04-14T04:00:45.503 回答
0

我在 Kotlin 中编写了一个简单的解析器,它并不完整。但是,它可以作为您自己实施的起点。

感谢@peter.murray.rust 提供的想法,它也受到h2database parser的启发。

它主要是逐个字符地读取 JSON 字符串(使用 StringReader)并尝试根据预期的 JSON 令牌对其进行解析。它正在实现读取令牌、未读字符并最终将结果转换为 AST 的功能。

你可以在 github上找到代码测试用例。

于 2019-06-12T22:53:41.387 回答