2017-08-15 29 views
6

निम्नलिखित POJOs को देखते हुए ..जैक्सन के साथ एक फ्लैट JSON ऑब्जेक्ट में एक सूची सामग्री को क्रमबद्ध करने के लिए कैसे?

public class City { 

    private String title; 
    private List<Person> people; 
} 

...

public class Person { 

    private String name; 
    private int age; 
} 

मैं निम्नलिखित उदाहरण के लिए कक्षाओं के Jackson serialize उदाहरणों जाने के लिए JSON चाहते हैं:

{ 
    "title" : "New York", 
    "personName_1" : "Jane Doe", 
    "personAge_1" : 42, 
    "personName_2" : "John Doe", 
    "personAge_2" : 23 
} 

जेएसओएन प्रारूप को बाहरी एपीआई द्वारा परिभाषित किया गया है जिसे मैं नहीं बदल सकता।

मैं पहले से ही पता चला है कि मैं इस तरह के रूप में एक कस्टम serializer साथ सूची क्षेत्र टिप्पणी कर सकते हैं:

@JsonSerialize(using = PeopleSerializer.class) 
private List<Person> people; 

... और यहाँ एक बुनियादी कार्यान्वयन मैंने कोशिश की है:

public class PeopleSerializer extends JsonSerializer<List<Person>> { 

    private static final int START_INDEX = 1; 

    @Override 
    public void serialize(List<Person> people, 
          JsonGenerator generator, 
          SerializerProvider provider) throws IOException { 
     for (int i = 0; i < people.size(); ++i) { 
      Person person = people.get(i); 
      int index = i + START_INDEX; 
      serialize(person, index, generator); 
     } 
    } 

    private void serialize(Person person, int index, JsonGenerator generator) throws IOException { 
     generator.writeStringField(getIndexedFieldName("personName", index), 
            person.getName()); 
     generator.writeNumberField(getIndexedFieldName("personAge", index), 
            person.getAge()); 
    } 

    private String getIndexedFieldName(String fieldName, int index) { 
     return fieldName + "_" + index; 
    } 

} 

हालांकि, इस एक के साथ विफल:

JsonGenerationException: Can not write a field name, expecting a value 

मैं भी जैक्सन के Converter इंटरफेस लेकिन था का उपयोग कर में देखा टी नेस्टेड सूची ऑब्जेक्ट्स को खोलने के लिए उपयुक्त नहीं है।

मैं भी @JsonUnwrapped के बारे में पता कर रहा हूँ, लेकिन यह सूची के साथ उपयोग के लिए डिज़ाइन नहीं किया गया है।

संबंधित पदों

संबंधित पोस्ट (अक्रमांकन)

संबंधित पुस्तकालय

+0

मुझे लगता है कि आप' City' वर्ग के लिए अपने 'JsonSerializer' लिखने के लिए है, यह है फ़ील्ड नाम 'लोग' तो मूल्य लिखने के लिए आपके कस्टम 'JsonSerializer' की अपेक्षा करता है। –

+0

@ug_ मैंने कोशिश की और '@JsonSerialize (= citySerializer.class का उपयोग करके) 'शहर' वर्ग को एनोटेट किया। कस्टम सीरिएलाइज़र के भीतर मैंने उपरोक्त 'पीपुल्सरिएलाइज़र' में किए गए कार्यों के समान ही 'लोगों' फ़ील्ड को क्रमबद्ध किया। Serialization एक ही त्रुटि संदेश के साथ विफल रहता है। – JJD

+0

@JJD - ऑफ-मौके पर एक आसान दृष्टिकोण है, आप जिस मूल समस्या को हल करने की कोशिश कर रहे हैं वह क्या है? मैं ऐसे मामले की कल्पना नहीं कर सकता जहां आपका लक्ष्य JSON संरचना पार्स करना आसान बनाता है ... – AjahnCharles

उत्तर

1

आप BeanSerializerModifier का उपयोग सीधे संशोधित करने के लिए कैसे एक संपत्ति का नाम और मान लिखे गए हैं कर सकते हैं। इसका उपयोग करके आप यह पता लगा सकते हैं कि कस्टम एनोटेशन मौजूद है या नहीं, इस मामले में मैंने @FlattenCollection नामक एक बनाया है। जब एनोटेशन मौजूद होता है तो सरणी या संग्रह सामान्य विधि का उपयोग नहीं किया जाता है बल्कि इसके बजाय कस्टम प्रॉपर्टी लेखक (FlattenCollectionPropertyWriter) द्वारा लिखा जाता है।

यह एनोटेशन 2 डी सरणी या अन्य किनारे के मामलों पर तोड़ने की संभावना है, मैंने उन लोगों का परीक्षण किया है, लेकिन संभवतः आप बिना किसी परेशानी के उनके लिए कोड कर सकते हैं, कम से कम एक सार्थक त्रुटि फेंक दें।

पूर्ण कार्य कोड है। उल्लेखनीय अंक

  • FlattenCollectionSerializerModifier.changeProperties
  • FlattenCollectionPropertyWriter.serializeAsField
  • जोड़ी Todos मैं आप के लिए वहाँ में डाल रहे हैं।

आउटपुट:

{ 
    "titleCity" : "New York", 
    "personName_1" : "Foo", 
    "personAge_1" : 123, 
    "personName_2" : "Baz", 
    "personAge_2" : 22 
} 

कोड: क्योंकि `के लिए serializer City` लिखते

import com.fasterxml.jackson.core.JsonGenerator; 
import com.fasterxml.jackson.databind.*; 
import com.fasterxml.jackson.databind.ser.*; 
import com.fasterxml.jackson.databind.util.NameTransformer; 

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
import java.util.*; 

public class SO45698499 { 


    public static void main(String [] args) throws Exception { 
     ObjectWriter writer = createMapper().writerWithDefaultPrettyPrinter(); 
     String val = writer.writeValueAsString(new City("New York", 
       Arrays.asList(new Person("Foo", 123), new Person("Baz", 22)))); 

     System.out.println(val); 
    } 


    /** 
    * Constructs our mapper with the serializer modifier in mind 
    * @return 
    */ 
    public static ObjectMapper createMapper() { 
     FlattenCollectionSerializerModifier modifier = new FlattenCollectionSerializerModifier(); 
     SerializerFactory sf = BeanSerializerFactory.instance.withSerializerModifier(modifier); 
     ObjectMapper mapper = new ObjectMapper(); 
     mapper.setSerializerFactory(sf); 

     return mapper; 
    } 

    @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 
    @Retention(RetentionPolicy.RUNTIME) 
    public @interface FlattenCollection { 
    } 

    /** 
    * Looks for the FlattenCollection annotation and modifies the bean writer 
    */ 
    public static class FlattenCollectionSerializerModifier extends BeanSerializerModifier { 

     @Override 
     public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { 
      for (int i = 0; i < beanProperties.size(); i++) { 
       BeanPropertyWriter writer = beanProperties.get(i); 
       FlattenCollection annotation = writer.getAnnotation(FlattenCollection.class); 
       if (annotation != null) { 
        beanProperties.set(i, new FlattenCollectionPropertyWriter(writer)); 
       } 
      } 
      return beanProperties; 
     } 
    } 

    /** 
    * Instead of writing a collection as an array, flatten the objects down into values. 
    */ 
    public static class FlattenCollectionPropertyWriter extends BeanPropertyWriter { 
     private final BeanPropertyWriter writer; 

     public FlattenCollectionPropertyWriter(BeanPropertyWriter writer) { 
      super(writer); 
      this.writer = writer; 
     } 

     @Override 
     public void serializeAsField(Object bean, 
            JsonGenerator gen, 
            SerializerProvider prov) throws Exception { 
      Object arrayValue = writer.get(bean); 

      // lets try and look for array and collection values 
      final Iterator iterator; 
      if(arrayValue != null && arrayValue.getClass().isArray()) { 
       // deal with array value 
       iterator = Arrays.stream((Object[])arrayValue).iterator(); 
      } else if(arrayValue != null && Collection.class.isAssignableFrom(arrayValue.getClass())) { 
       iterator = ((Collection)arrayValue).iterator(); 
      } else { 
       iterator = null; 
      } 

      if(iterator == null) { 
       // TODO: write null? skip? dunno, you gonna figure this one out 
      } else { 
       int index=0; 
       while(iterator.hasNext()) { 
        index++; 
        Object value = iterator.next(); 
        if(value == null) { 
         // TODO: skip null values and still increment or maybe dont increment? You decide 
        } else { 
         // TODO: OP - update your prefix/suffix here, its kinda weird way of making a prefix 
         final String prefix = value.getClass().getSimpleName().toLowerCase(); 
         final String suffix = "_"+index; 
         prov.findValueSerializer(value.getClass()) 
           .unwrappingSerializer(new FlattenNameTransformer(prefix, suffix)) 
           .serialize(value, gen, prov); 
        } 
       } 
      } 
     } 
    } 

    public static class FlattenNameTransformer extends NameTransformer { 

     private final String prefix; 
     private final String suffix; 

     public FlattenNameTransformer(String prefix, String suffix) { 
      this.prefix = prefix; 
      this.suffix = suffix; 
     } 

     @Override 
     public String transform(String name) { 
      // captial case the first letter, to prepend the suffix 
      String transformedName = Character.toUpperCase(name.charAt(0)) + name.substring(1); 
      return prefix + transformedName + suffix; 
     } 
     @Override 
     public String reverse(String transformed) { 
      if (transformed.startsWith(prefix)) { 
       String str = transformed.substring(prefix.length()); 
       if (str.endsWith(suffix)) { 
        return str.substring(0, str.length() - suffix.length()); 
       } 
      } 
      return null; 
     } 
     @Override 
     public String toString() { return "[FlattenNameTransformer('"+prefix+"','"+suffix+"')]"; } 
    } 


    /*=============================== 
    * POJOS 
    ===============================*/ 
    public static class Person { 
     private String name; 
     private int age; 

     public Person(String name, int age) { 
      this.name = name; 
      this.age = age; 
     } 

     public String getName() { 
      return name; 
     } 

     public void setName(String name) { 
      this.name = name; 
     } 

     public int getAge() { 
      return age; 
     } 

     public void setAge(int age) { 
      this.age = age; 
     } 
    } 

    public static class City { 
     private String titleCity; 
     private List<Person> people; 

     public City(String title, List<Person> people) { 
      this.titleCity = title; 
      this.people = people; 
     } 

     public String getTitleCity() { 
      return titleCity; 
     } 

     public void setTitleCity(String titleCity) { 
      this.titleCity = titleCity; 
     } 

     @FlattenCollection 
     public List<Person> getPeople() { 
      return people; 
     } 

     public void setPeople(List<Person> people) { 
      this.people = people; 
     } 
    } 
} 
+0

वाह! प्रभावित! Serialization को अनुकूलित करने के लिए आप इस विकल्प के बारे में कहां से पता चला? - मैंने कक्षाओं को सफलतापूर्वक एकीकृत किया है, जहां तक ​​'FlattenCollectionPropertyWriter' को तत्काल किया गया है, हालांकि 'serializeAsField()' ** ** ** नहीं है। – JJD

+0

@JJD मैं भूल जाता हूं कि मुझे मूल रूप से यह कहां मिला, मैंने अपनी आईडी मेमोरी में खोज की जाने वाली जावा डॉक की उचित मात्रा के साथ अपनी याददाश्त को ताज़ा कर दिया।यदि 'serializeAsField()' नहीं कहा जा रहा है तो आप 'BeanPropertyWriter' पर अन्य 'seralizeAs ***' विधियों की जांच कर सकते हैं। यह भी सुनिश्चित करें कि 'FlattenCollectionPropertyWriter' बनाया जा रहा है। उन विधियों को उनके जावाडोक द्वारा वर्णित विशिष्ट परिदृश्यों में बुलाया जाएगा, हालांकि मुझे यह समझने में परेशानी हो रही है कि उन्हें इस संपत्ति के लिए क्यों बुलाया जाएगा, लेकिन ब्रेकपॉइंट डालने के लिए यह एक अच्छी जगह होगी। –

+0

किसी भी तरह आज विधि लागू की जाती है। मैं वास्तव में नहीं बता सकता कि कल क्या गलत हुआ। शायद, यह बहुत देर हो चुकी थी। - आपके उत्तर के लिए धन्यवाद। बड़ी मदद! – JJD

1

मैं क्षेत्र स्तरीय एनोटेशन संदेह पर this link आधार पर केवल मूल्य नहीं पूरे गुण लेखन प्रतिनिधियों।

एक (बल्कि kludgey) वैकल्पिक हल पूरे शहर वर्ग के लिए एक कस्टम serializer के लिए हो सकता है:

@JsonSerialize(using = CitySerializer.class) 
public class City { 
    private String title; 
    @JsonIgnore 
    private List<Person> people; 
} 

...और फिर

public class CitySerializer extends JsonSerializer<City> { 

    private static final int START_INDEX = 1; 

    @Override 
    public void serialize(City city, 
          JsonGenerator generator, 
          SerializerProvider provider) throws IOException { 
     generator.writeStartObject(); 

     // Write all properties (except ignored) 
     JavaType javaType = provider.constructType(City.class); 
     BeanDescription beanDesc = provider.getConfig().introspect(javaType); 
     JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider, 
       javaType, 
       beanDesc); 
     serializer.unwrappingSerializer(null).serialize(value, jgen, provider);` 

     // Custom serialization of people 
     List<Person> people = city.getPeople(); 
     for (int i = 0; i < people.size(); ++i) { 
      Person person = people.get(i); 
      int index = i + START_INDEX; 
      serialize(person, index, generator); 
     } 

     generator.writeEndObject(); 
    } 

    private void serialize(Person person, int index, JsonGenerator generator) throws IOException { 
     generator.writeStringField(getIndexedFieldName("personName", index), 
            person.getName()); 
     generator.writeNumberField(getIndexedFieldName("personAge", index), 
            person.getAge()); 
    } 

    private String getIndexedFieldName(String fieldName, int index) { 
     return fieldName + "_" + index; 
    } 

} 
+0

धन्यवाद, यह आशाजनक लग रहा है। हालांकि, एक बड़ी कमी यह है कि मैं ** लोगों को 'लोगों' फ़ील्ड के लिए विशेष हैंडलिंग परिभाषित नहीं कर सकता हूं। मुझे 'सिटी' कक्षा के ** अन्य सभी ** फ़ील्ड मैन्युअल रूप से लिखने की आवश्यकता है (वास्तव में और अधिक हैं)। जब भी 'सिटी' कक्षा में परिवर्तन होता है तो मुझे भी धारावाहिक को अद्यतन करना होगा। इससे इसे बनाए रखना मुश्किल हो जाता है। यह भी चर्चा की [यहां] (https://stackoverflow.com/questions/14714328/jackson-how-to-add-custom-property-to-the-json-without-modifying-the-pojo)। – JJD

+0

यह बहुत अच्छा होगा अगर मैं ** ** 'सिटी' के क्रमबद्धकरण को रोक सकता हूं और 'लोगों' फ़ील्ड के कस्टम JSON प्रतिनिधित्व मैन्युअल रूप से लिख सकता हूं। इसे एक विशिष्ट क्षेत्र के लिए * प्रतिनिधिमंडल धारावाहिक * भी कहा जा सकता है। शायद, मैं सिर्फ गलत शब्दों से खोज रहा हूँ। – JJD

+0

@JJD - मैं पूरी तरह से आपसे सहमत हूं। मैंने इसमें एक स्टैब किया है, लेकिन मैं अभी आसानी से परीक्षण नहीं कर सकता। – AjahnCharles

संबंधित मुद्दे