【问题标题】:Use non-JSON format for Jackson对 Jackson 使用非 JSON 格式
【发布时间】:2017-12-27 19:23:52
【问题描述】:

我正在使用 JAX-RS(带有 RESTEasy)和最新版本的 Jackson 来编组 JSON 中的对象。例如,我只需将返回内容类型指定为application/json,然后将我的对象转换为 JSON。我可以使用 Jackson 注释来调整要序列化的属性、它们的命名方式等。

我有一个自定义的基于文本的 Foo 格式 text/foo(为了讨论),它类似于 JSON,但不同,尤其是在对象和属性的分隔方式上。我希望能够将 text/foo 指定为返回内容类型,并让我的对象自动序列化为 Foo 格式。我希望我的编组库独立于 JAX-RS 实现。

我的第一个想法是以某种方式利用 Jackson,以便我可以将 Jackson 的注释用于属性名称等。这样,如果我将内容类型更改为 @987654326,则使用 Jackson 为 JSON 配置的某些对象将@,被序列化为 Foo 格式,但仍使用 Jackson 注释指示的属性名称。

事实证明,Jackson 相当复杂,有各种 API,而且从哪里开始并不明显。

  • 是否可以利用 Jackson 将对象序列化为非 JSON 格式?
  • 我应该使用哪个 Jackson API(树模型、流式 API 或数据绑定)?
  • 是否有一些现有的非 JSON Jackson 库可以作为示例来帮助我入门?
  • 以这种方式使用Jackson 是否比它的价值更麻烦,我是否应该只编写JAX-RS MessageBodyReaderMessageBodyWriter 实现,完全跳过Jackson? (我犹豫要不要走这条路,因为我必须从头开始编写所有反射逻辑,编写自己的注释来指定属性名称更改等,并放弃所有 Jackson 的类型插件基础设施。)

【问题讨论】:

  • 你应该看看jackson-dataformats-text 项目。这支持 Jackson 到 YAML、CSV 等...您也许可以以此为起点来实现自己的格式。
  • @Jonathan,感谢您的参考,但是……是的,我们立即在 Google 搜索中发现了这一点。它看起来很有希望,但当你开始使用它时,它对于比 JSON 更丰富的新格式没有用处。
  • @GarretWilson 您能否提供有关您的自定义格式的更多详细信息?您似乎已经完成了研究,因此如果没有详细信息,就很难决定从哪里开始。
  • @Sheepy,格式为 SURF:urf.io/surf
  • @GarretWilson 我检查了 SURF。它的表达式比 JSON 丰富得多,所以我认为你不能只在 Jackson 之上插入一个层。这就像改变火车的轮子让它像汽车一样运行,但火车不是汽车,你最终会遇到很多困难。你最终会扩展/重写杰克逊的许多部分。

标签: java json jackson jax-rs


【解决方案1】:

Jackson API 的优势

  • 是可用于处理 JSON 文件的最快库之一。
  • 具有更广泛的注解支持,甚至接受来自其他 API(如 JAXB)的注解。
  • 与 JAX-RS 有更好的集成。

流式传输(核心)

这是 Jackson 的核心,它是解析器/序列化器所在的地方,处理输入/输出数据的流处理。

  • 这是解析/序列化数据的最快方式。
  • 不是处理 JSON 内容的最便捷方式。
    • 内容必须按照与输入/输出完全相同的顺序进行处理才能正常工作,即不能随机访问。
    • 更多地被中间件和框架使用,但在应用程序中不是很常见。

这是我们必须实现的 API,以拥有支持我们想要的数据格式的完整功能版本的 Jackson。它也是唯一由 XML、Java 属性、YAML 等其他数据格式实现的 API。

实现此 API 后将自动支持 DataBinding 和 TreeModel。

GSON也和Jackson一起被研究过,但由于与Jackson相似,表现欠佳,所以Jackson是本报告的主要对象。

性能的差异是由于这些 API 处理数据的方式,Jackson 最初使用的是流,这是一种快速的低级方法,它使 Jackson 成为最快的处理 JSON 的 API当它启动时。

虽然 GSON 也开始使用这种方法来处理数据,但根据基准测试,Jackson 仍然是最快的 API,可以在参考部分找到。

树模型

这是提供 JSON 文档的可变内存树表示的 api 部分。

当处理一些类似于 XML 的数据格式时,这个 API 是最灵活的。

数据绑定

这是 API 的一部分,它根据属性访问器约定或注释将 JSON 与 POJO 相互转换。

处理 JSON 内容的最便捷方式。 它允许在 JSON 数据和 JAVA 对象之间进行转换。 类似于 TreeModel,但使用 Java 对象而不是基于节点的模型。

这个API在应用方面是最常用的


Jackson Streaming 实施注意事项

由于这是我们唯一需要实现的 API,我们将更加关注这一点。

Streaming API 的实现使用所谓的JsonToken 来识别即将进入流中的 JSON 对象,即输入/输出数据。

JsonToken 枚举具有以下定义:

NOT_AVAILABLE(null), 
START_OBJECT("{"), 
END_OBJECT("}"), 
START_ARRAY("["), 
END_ARRAY("]"), 
FIELD_NAME(null), 
VALUE_STRING(null), 
VALUE_NUMBER_INT(null), 
VALUE_NUMBER_FLOAT(null), 
VALUE_TRUE("true"), 
VALUE_FALSE("false"), 
VALUE_NULL("null")

这些令牌可以通过所谓的ReadContext 检索。 ReadContext 有一些常量来定义自己的类型。

内部用于表示JsonStreamContext 类型的常量。

protected final static int TYPE_ROOT = 0; // Means the root element.
protected final static int TYPE_ARRAY = 1; // Means the array elements.
protected final static int TYPE_OBJECT = 2; // Means the JSON object elements.

Java Properties 数据格式的 ReadContext 的构造方法片段。

JPropReadContext extends JsonStreamContext {
    // Static factory method used to create the JPropReadContext instance.
    public static JPropReadContext create(JPropNode root) {
        if (root.isArray()) { // can this ever occur?
                return new ArrayContext(null, root);
        }
        return new ObjectContext(null, root);
    }
    …
    ArrayContext extends JPropReadContext {
        // Constructor for ArrayContext.
        public ArrayContext(JPropReadContext p, JPropNode arrayNode) {
            super(JsonStreamContext.TYPE_ARRAY, p, arrayNode);
            _contents = arrayNode.arrayContents();
            _state = STATE_START;
        }
        …
    }
    …
    ObjectContext extends JPropReadContext {
        // Constructor for ObjectContext.
        public ObjectContext(JPropReadContext p, JPropNode objectNode) {
            super(JsonStreamContext.TYPE_OBJECT, p, objectNode);
            _contents = objectNode.objectContents();
            _state = STATE_START;
        }
        …
    }
    …
}

JPropReadContext只是扩展JsonStreamContext,所以它需要从JsonStreamContext传递一个常量给它,指示上下文的类型,如果它是根、数组或对象。常量是一个简单的 int 值,所以在此之前没有问题。

这里是相同的信息,但现在是 XML 数据格式。

…
// Static factory method used to create the XmlReadContext instance of type ROOT.
public static XmlReadContext createRootContext(int lineNr, int colNr) {
    return new XmlReadContext(null, TYPE_ROOT, lineNr, colNr);
}

// Static factory method used to create the XmlReadContext instance of type ARRAY.
public final XmlReadContext createChildArrayContext(int lineNr, int colNr) {
    XmlReadContext ctxt = _child;       
    if (ctxt == null) {
        _child = ctxt = new XmlReadContext(this, TYPE_ARRAY, lineNr, colNr);
        return ctxt;
    }
    ctxt.reset(TYPE_ARRAY, lineNr, colNr);
    return ctxt;
}

// Static factory method used to create the XmlReadContext instance of type OBJECT.
public final XmlReadContext createChildObjectContext(int lineNr, int colNr) {
    XmlReadContext ctxt = _child;
    if (ctxt == null) {
        _child = ctxt = new XmlReadContext(this, TYPE_OBJECT, lineNr, colNr);
        return ctxt;
    }
    ctxt.reset(TYPE_OBJECT, lineNr, colNr);
    return ctxt;
}
…

Jackson 的其他数据格式的实现只是简单地使用这些常量和JsonToken,就好像它们的格式是 Json 实例一样,换句话说,它伪造自己的格式以使其看起来像 Json 实例杰克逊,这不是一个干净的方式。例如,他们可以使用TYPE_OBJECT 来表示<element>value<element>property = "string containing the value of the property." 之类的东西。

这适用于与 Json 具有相同/更少功能的格式,因为每个功能都可以实现,但对于具有更多功能的数据格式是不可能的。


此报告是为 GlobalMentor Inc. 制作的,作为 https://globalmentor.atlassian.net/browse/URF-33 的一部分,我们尝试在杰克逊实现对 SURF data format 的支持。

【讨论】:

    猜你喜欢
    • 2014-12-22
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 2015-06-06
    相关资源
    最近更新 更多