2011-01-24 43 views
70

Tôi đang cố gắng bao gồm JSON thô bên trong một đối tượng Java khi đối tượng được (de) được tuần tự hóa bằng Jackson. Để kiểm tra chức năng này, tôi đã viết bài kiểm tra sau:Làm cách nào để đưa JSON thô vào một đối tượng bằng Jackson?

public static class Pojo { 
    public String foo; 

    @JsonRawValue 
    public String bar; 
} 

@Test 
public void test() throws JsonGenerationException, JsonMappingException, IOException { 

    String foo = "one"; 
    String bar = "{\"A\":false}"; 

    Pojo pojo = new Pojo(); 
    pojo.foo = foo; 
    pojo.bar = bar; 

    String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}"; 

    ObjectMapper objectMapper = new ObjectMapper(); 
    String output = objectMapper.writeValueAsString(pojo); 
    System.out.println(output); 
    assertEquals(json, output); 

    Pojo deserialized = objectMapper.readValue(output, Pojo.class); 
    assertEquals(foo, deserialized.foo); 
    assertEquals(bar, deserialized.bar); 
} 

Mã kết quả đầu ra dòng sau:

{"foo":"one","bar":{"A":false}} 

Các JSON là chính xác làm thế nào tôi muốn điều cần xem xét. Thật không may, mã không thành công với một ngoại lệ khi cố đọc JSON trở lại đối tượng. Dưới đây là những ngoại lệ:

org.codehaus.jackson.map.JsonMappingException: Không thể deserialize thể hiện của java.lang.String ra khỏi START_OBJECT thẻ tại [Nguồn: [email protected]; dòng: 1, cột: 13] (thông qua chuỗi tham chiếu: com.tnal.prism.cobalt.gather.testing.Pojo ["bar"])

Tại sao Jackson hoạt động tốt theo một hướng nhưng không thành công khi đi theo hướng khác? Có vẻ như nó sẽ có thể lấy lại đầu ra của chính nó làm đầu vào một lần nữa. Tôi biết những gì tôi đang cố gắng làm là không chính thống (lời khuyên chung là tạo một đối tượng bên trong cho bar có một thuộc tính có tên là A), nhưng tôi không muốn tương tác với JSON này chút nào. Mã của tôi đang hoạt động như một mã thông qua cho mã này - tôi muốn lấy JSON này và gửi nó trở lại mà không cần chạm vào một thứ, bởi vì khi các thay đổi JSON tôi không muốn mã của tôi cần sửa đổi.

Cảm ơn lời khuyên.

EDIT: Tạo Pojo là lớp tĩnh, gây ra lỗi khác.

Trả lời

39

@JsonRawValue là dành cho chỉ serialization-side, vì theo hướng ngược lại là một chút phức tạp hơn để xử lý. Trong thực tế nó đã được thêm vào để cho phép tiêm nội dung được mã hóa trước.

Tôi đoán có thể thêm hỗ trợ để đảo ngược, mặc dù điều đó sẽ khá khó xử: nội dung sẽ phải được phân tích cú pháp và sau đó được viết lại thành biểu mẫu "thô", có thể có hoặc không giống nhau (vì trích dẫn ký tự có thể khác nhau). Điều này cho trường hợp chung. Nhưng có lẽ nó sẽ có ý nghĩa đối với một số tập hợp các vấn đề. Tuy nhiên, tôi nghĩ rằng một work-around cho trường hợp cụ thể của bạn sẽ được chỉ định loại như 'java.lang.Object', vì điều này sẽ làm việc ok: cho serialization, String sẽ được sản lượng như là, và cho deserialization, nó sẽ được deserialized như một bản đồ. Trên thực tế bạn có thể muốn có getter riêng biệt/setter nếu như vậy; getter sẽ trả về String cho serialization (và cần @JsonRawValue); và setter sẽ lấy Map hoặc Object. Bạn có thể mã hóa lại nó thành một Chuỗi nếu điều đó có ý nghĩa.

+0

Điều này hoạt động như một sự quyến rũ; xem phản ứng của tôi cho mã (_formatting trong các ý kiến ​​là awgul_). –

+0

Tôi đã có một trường hợp sử dụng khác cho việc này. Có vẻ như nếu chúng ta không muốn tạo ra rất nhiều chuỗi rác trong deser/ser, chúng ta sẽ có thể chỉ passthrough một chuỗi như vậy. Tôi đã thấy một chuỗi theo dõi điều này, nhưng có vẻ như không có hỗ trợ gốc nào có thể. Hãy xem http://markmail.org/message/qjncoohoalre4vd2#query:+page:1+mid:6ol54pw3zsr4jbhr+state:results – Sid

+0

@Sid không có cách nào để thực hiện điều đó và mã hóa hiệu quả cả hai. Để hỗ trợ chuyển giao thông qua các mã thông báo chưa được xử lý sẽ yêu cầu bổ sung trạng thái giữ, làm cho việc phân tích cú pháp "thường xuyên" phần nào kém hiệu quả hơn. Nó là loại giống như tối ưu hóa giữa mã thông thường và ngoại lệ ném: để hỗ trợ sau này cho biết thêm chi phí cho trước đây. Jackson chưa được thiết kế để cố gắng giữ cho đầu vào chưa được xử lý sẵn có; nó sẽ được tốt đẹp để có nó (cho các thông báo lỗi, quá) xung quanh, nhưng sẽ yêu cầu cách tiếp cận khác nhau. – StaxMan

3

Đây là vấn đề với các lớp bên trong của bạn. Lớp học Pojo là lớp học non-static inner class của lớp kiểm tra của bạn và Jackson không thể khởi tạo lớp học đó. Vì vậy, nó có thể serialize, nhưng không deserialize.

Xác định lại lớp học của bạn như thế này:

public static class Pojo { 
    public String foo; 

    @JsonRawValue 
    public String bar; 
} 

Lưu ý việc bổ sung các static

+0

Cảm ơn. Điều đó đã cho tôi một bước xa hơn, nhưng bây giờ tôi nhận được một lỗi khác.Tôi sẽ cập nhật bài đăng gốc với lỗi mới. – bhilstrom

41

Sau @StaxMan câu trả lời, tôi đã thực hiện các công việc sau như một nét duyên dáng:

public class Pojo { 
    Object json; 

    @JsonRawValue 
    public String getJson() { 
    // default raw value: null or "[]" 
    return json == null ? null : json.toString(); 
    } 

    public void setJson(JsonNode node) { 
    this.json = node; 
    } 
} 

Và, để trung thành với câu hỏi ban đầu, đây là thử nghiệm làm việc:

public class PojoTest { 
    ObjectMapper mapper = new ObjectMapper(); 

    @Test 
    public void test() throws IOException { 
    Pojo pojo = new Pojo("{\"foo\":18}"); 

    String output = mapper.writeValueAsString(pojo); 
    assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}"); 

    Pojo deserialized = mapper.readValue(output, Pojo.class); 
    assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}"); 
    // deserialized.json == {"foo":18} 
    } 
} 
+1

Tôi đã không thử, nhưng nó sẽ làm việc: 1) thực hiện một nút JsonNode thay vì Object json 2) sử dụng node.asText() thay vì toString(). Tôi không chắc chắn về thứ hai mặc dù. –

+0

Tôi tự hỏi tại sao 'getJson()' lại trả về một 'Chuỗi'. Nếu nó vừa trả về 'JsonNode' đã được thiết lập thông qua setter, nó sẽ được sắp xếp theo ý muốn, phải không? – sorrymissjackson

+0

@VadimKirilchuk 'node.asText()' trả về giá trị rỗng đối diện 'toString()'. –

1

tôi có cùng một vấn đề. Tôi tìm thấy giải pháp trong bài đăng này: Parse JSON tree to plain class using Jackson or its alternatives

Kiểm tra câu trả lời cuối cùng. Bằng cách xác định một setter tùy chỉnh cho thuộc tính có tham số JsonNode và gọi phương thức toString trên jsonNode để thiết lập thuộc tính String, tất cả đều hoạt động.

24

tôi đã có thể làm điều này với một deserializer tùy chỉnh (cắt và dán từ here)

package etc; 

import java.io.IOException; 

import com.fasterxml.jackson.core.JsonParser; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.core.TreeNode; 
import com.fasterxml.jackson.databind.DeserializationContext; 
import com.fasterxml.jackson.databind.JsonDeserializer; 

/** 
* Keeps json value as json, does not try to deserialize it 
* @author roytruelove 
* 
*/ 
public class KeepAsJsonDeserialzier extends JsonDeserializer<String> { 

    @Override 
    public String deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException { 

     TreeNode tree = jp.getCodec().readTree(jp); 
     return tree.toString(); 
    } 
} 
+3

Tuyệt vời thật đơn giản. IMO này phải là câu trả lời chính thức. Tôi đã thử với một cấu trúc rất phức tạp có chứa mảng, subobjects, vv Có lẽ bạn chỉnh sửa câu trả lời của bạn và thêm rằng các thành viên String được deserialized nên được chú thích bởi @ JsonDeserialize (using = KeepAsJsonDeserialzier.class). (và sửa lỗi đánh máy trong tên lớp của bạn ;-) – Heri

+0

hoạt động cho Deserializion. Làm thế nào về cho Serialization nguyên json vào một pojo? Làm thế nào để được thực hiện – xtrakBandit

+3

@xtrakBandit cho Tuần tự hóa, sử dụng '@ JsonRawValue' – smartwjw

0

Sử dụng một đối tượng hoạt động tốt cả hai cách ... Phương pháp này có một chút overhead deserializing giá trị nguyên trong hai lần.

ObjectMapper mapper = new ObjectMapper(); 
RawJsonValue value = new RawJsonValue(); 
value.setRawValue(new RawHello(){{this.data = "universe...";}}); 
String json = mapper.writeValueAsString(value); 
System.out.println(json); 
RawJsonValue result = mapper.readValue(json, RawJsonValue.class); 
json = mapper.writeValueAsString(result.getRawValue()); 
System.out.println(json); 
RawHello hello = mapper.readValue(json, RawHello.class); 
System.out.println(hello.data); 

RawHello.java

public class RawHello { 

    public String data; 
} 

RawJsonValue.java

public class RawJsonValue { 

    private Object rawValue; 

    public Object getRawValue() { 
     return rawValue; 
    } 

    public void setRawValue(Object value) { 
     this.rawValue = value; 
    } 
} 
10

@JsonSetter có thể giúp đỡ. Xem mẫu của tôi ('dữ liệu' có nghĩa vụ phải chứa chưa phân tích JSON):

class Purchase 
{ 
    String data; 

    @JsonProperty("signature") 
    String signature; 

    @JsonSetter("data") 
    void setData(JsonNode data) 
    { 
     this.data = data.toString(); 
    } 
} 
2

Thêm vào Roy Truelove 's lớn answer, đây là cách để tiêm deserialiser tùy chỉnh để đáp ứng với sự xuất hiện của @JsonRawValue:

import com.fasterxml.jackson.databind.Module; 

@Component 
public class ModuleImpl extends Module { 

    @Override 
    public void setupModule(SetupContext context) { 
     context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl()); 
    } 
} 

import java.util.Iterator; 

import com.fasterxml.jackson.annotation.JsonRawValue; 
import com.fasterxml.jackson.databind.BeanDescription; 
import com.fasterxml.jackson.databind.DeserializationConfig; 
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder; 
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; 
import com.fasterxml.jackson.databind.deser.SettableBeanProperty; 

public class BeanDeserializerModifierImpl extends BeanDeserializerModifier { 
    @Override 
    public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { 
     Iterator<SettableBeanProperty> it = builder.getProperties(); 
     while (it.hasNext()) { 
      SettableBeanProperty p = it.next(); 
      if (p.getAnnotation(JsonRawValue.class) != null) { 
       builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true); 
      } 
     } 
     return builder; 
    } 
} 
1

giải pháp dễ dàng này đã làm việc cho tôi:

public class MyObject { 
    private Object rawJsonValue; 

    public Object getRawJsonValue() { 
     return rawJsonValue; 
    } 

    public void setRawJsonValue(Object rawJsonValue) { 
     this.rawJsonValue = rawJsonValue; 
    } 
} 

Vì vậy, tôi có thể lưu trữ giá trị thô của JSON trong biến rawJsonValue và sau đó không có vấn đề gì để deserialize nó (như đối tượng) với các trường khác về JSON và gửi qua REST của tôi. Sử dụng @JsonRawValue didnt đã giúp tôi vì JSON được lưu trữ đã được deserialized như String, không phải là đối tượng, và đó không phải là những gì tôi muốn.

1

Điều này thậm chí làm việc trong một thực thể JPA:

private String json; 

@JsonRawValue 
public String getJson() { 
    return json; 
} 

public void setJson(final String json) { 
    this.json = json; 
} 

@JsonProperty(value = "json") 
public void setJsonRaw(JsonNode jsonNode) { 
    setJson(jsonNode.toString()); 
} 
0

Dưới đây là một ví dụ làm việc đầy đủ về cách sử dụng module Jackson để làm @JsonRawValue công việc cả hai cách (serialization và deserialization):

public class JsonRawValueDeserializerModule extends SimpleModule { 

    public JsonRawValueDeserializerModule() { 
     setDeserializerModifier(new JsonRawValueDeserializerModifier()); 
    } 

    private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier { 
     @Override 
     public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { 
      builder.getProperties().forEachRemaining(property -> { 
       if (property.getAnnotation(JsonRawValue.class) != null) { 
        builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true); 
       } 
      }); 
      return builder; 
     } 
    } 

    private static class JsonRawValueDeserializer extends JsonDeserializer<String> { 
     private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer(); 

     @Override 
     public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 
      return p.readValueAsTree().toString(); 
     } 
    } 
} 

Sau đó, bạn có thể đăng ký mô-đun sau khi tạo ObjectMapper:

ObjectMapper objectMapper = new ObjectMapper(); 
objectMapper.registerModule(new JsonRawValueDeserializerModule()); 

String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}"; 
Pojo deserialized = objectMapper.readValue(json, Pojo.class); 
Các vấn đề liên quan