JSONPath-简单入门

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/koflance/article/details/63262484

1. 介绍

类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的。其表达式可以接受“dot–notation”和“bracket–notation”格式,例如$.store.book[0].title、$[‘store’][‘book’][0][‘title’]

2. 操作符

符号描述
$查询的根节点对象,用于表示一个json数据,可以是数组或对象
@过滤器断言(filter predicate)处理的当前节点对象,类似于java中的this字段
*通配符,可以表示一个名字或数字
..可以理解为递归搜索,Deep scan. Available anywhere a name is required.
.<name>表示一个子节点
[‘<name>’ (, ‘<name>’)]表示一个或多个子节点
[<number> (, <number>)]表示一个或多个数组下标
[start:end]数组片段,区间为[start,end),不包含end
[?(<expression>)]过滤器表达式,表达式结果必须是boolean

3. 函数

可以在JsonPath表达式执行后进行调用,其输入值为表达式的结果。

名称描述输出
min()获取数值类型数组的最小值Double
max()获取数值类型数组的最大值Double
avg()获取数值类型数组的平均值Double
stddev()获取数值类型数组的标准差Double
length()获取数值类型数组的长度Integer

4. 过滤器

过滤器是用于过滤数组的逻辑表达式,一个通常的表达式形如:[?(@.age > 18)],可以通过逻辑表达式&&或||组合多个过滤器表达式,例如[?(@.price < 10 && @.category == ‘fiction’)],字符串必须用单引号或双引号包围,例如[?(@.color == ‘blue’)] or [?(@.color == “blue”)]。

操作符描述
==等于符号,但数字1不等于字符1(note that 1 is not equal to ‘1’)
!=不等于符号
<小于符号
<=小于等于符号
>大于符号
>=大于等于符号
=~判断是否符合正则表达式,例如[?(@.name =~ /foo.*?/i)]
in所属符号,例如[?(@.size in [‘S’, ‘M’])]
nin排除符号
sizesize of left (array or string) should match right
empty判空符号

5. 示例

{
    "store": {
        "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99
            },
            {
                "category": "fiction",
                "author": "Herman Melville",
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            },
            {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    },
    "expensive": 10
}
 
 
    JsonPath表达式结果
    $.store.book[*].author

    $..author
    [
    “Nigel Rees”,
    “Evelyn Waugh”,
    “Herman Melville”,
    “J. R. R. Tolkien”
    ]
    $.store.* 显示所有叶子节点值[
    [
    {
    ”category” : “reference”,
    ”author” : “Nigel Rees”,
    ”title” : “Sayings of the Century”,
    ”price” : 8.95
    },
    {
    ”category” : “fiction”,
    ”author” : “Evelyn Waugh”,
    ”title” : “Sword of Honour”,
    ”price” : 12.99
    },
    {
    ”category” : “fiction”,
    ”author” : “Herman Melville”,
    ”title” : “Moby Dick”,
    ”isbn” : “0-553-21311-3”,
    ”price” : 8.99
    },
    {
    ”category” : “fiction”,
    ”author” : “J. R. R. Tolkien”,
    ”title” : “The Lord of the Rings”,
    ”isbn” : “0-395-19395-8”,
    ”price” : 22.99
    }
    ],
    {
    ”color” : “red”,
    ”price” : 19.95
    }
    ]
    $.store..price[
    8.95,
    12.99,
    8.99,
    22.99,
    19.95
    ]
    $..book[0,1]

    $..book[:2]
    [
    {
    ”category” : “reference”,
    ”author” : “Nigel Rees”,
    ”title” : “Sayings of the Century”,
    ”price” : 8.95
    },
    {
    ”category” : “fiction”,
    ”author” : “Evelyn Waugh”,
    ”title” : “Sword of Honour”,
    ”price” : 12.99
    }
    ]
    $..book[-2:]获取最后两本书
    $..book[2:][
    {
    ”category” : “fiction”,
    ”author” : “Herman Melville”,
    ”title” : “Moby Dick”,
    ”isbn” : “0-553-21311-3”,
    ”price” : 8.99
    },
    {
    ”category” : “fiction”,
    ”author” : “J. R. R. Tolkien”,
    ”title” : “The Lord of the Rings”,
    ”isbn” : “0-395-19395-8”,
    ”price” : 22.99
    }
    ]
    $..book[?(@.isbn)]所有具有isbn属性的书
    $.store.book[?(@.price < 10)]所有价格小于10的书
    $..book[?(@.price <= $[‘expensive’])]所有价格低于expensive字段的书
    $..book[?(@.author =~ /.*REES/i)]所有符合正则表达式的书
    [
    {
    ”category” : “reference”,
    ”author” : “Nigel Rees”,
    ”title” : “Sayings of the Century”,
    ”price” : 8.95
    }
    ]
    $..*返回所有
    $..book.length()[
    4
    ]

    测试请点击http://jsonpath.herokuapp.com/?path=$.store.book[*].author

    6. 常见用法

    通常是直接使用静态方法API进行调用,例如:

    String json = "...";
    
    List<String> authors = JsonPath.read(json, "$.store.book[*].author");
     
     
    • 1
    • 2
    • 3

    但以上方式仅仅适用于解析一次json的情况,如果需要对同一个json解析多次,不建议使用,因为每次read都会重新解析一次json,针对此种情况,建议使用ReadContext、WriteContext,例如:

    String json = "...";
    
    ReadContext ctx = JsonPath.parse(json);
    
    List<String> authorsOfBooksWithISBN = ctx.read("$.store.book[?(@.isbn)].author");
    
    
    List<Map<String, Object>> expensiveBooks = JsonPath
                                .using(configuration)
                                .parse(json)
                                .read("$.store.book[?(@.price > 10)]", List.class);
     
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    7. 返回值是什么?

    通常read后的返回值会进行自动转型到指定的类型,对应明确定义definite的表达式,应指定其对应的类型,对于indefinite含糊表达式,例如包括..、?(<expression>)、[<number>, <number> (, <number>)],通常应该使用数组。如果需要转换成具体的类型,则需要通过configuration配置mappingprovider,如下:

    String json = "{\"date_as_long\" : 1411455611975}";
    //使用JsonSmartMappingProvider
    Date date = JsonPath.parse(json).read("$['date_as_long']", Date.class);
    //使用GsonMappingProvider
    Book book = JsonPath.parse(json).read("$.store.book[0]", Book.class);
     
     
    • 1
    • 2
    • 3
    • 4
    • 5

    8. MappingProvider SPI反序列化器

    这里写图片描述

    其中JsonSmartMappingProvider提供了如下基本数据类型的转换,此provider是默认设置的,在Configuration.defaultConfiguration()中返回的DefaultsImpl类,使用的就是JsonSmartMappingProvider。

    DEFAULT.registerReader(Long.class, new LongReader());
    DEFAULT.registerReader(long.class, new LongReader());
    DEFAULT.registerReader(Integer.class, new IntegerReader());
    DEFAULT.registerReader(int.class, new IntegerReader());
    DEFAULT.registerReader(Double.class, new DoubleReader());
    DEFAULT.registerReader(double.class, new DoubleReader());
    DEFAULT.registerReader(Float.class, new FloatReader());
    DEFAULT.registerReader(float.class, new FloatReader());
    DEFAULT.registerReader(BigDecimal.class, new BigDecimalReader());
    DEFAULT.registerReader(String.class, new StringReader());
    DEFAULT.registerReader(Date.class, new DateReader());
     
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    切换Provider,如下:

    Configuration.setDefaults(new Configuration.Defaults() {
    
        private final JsonProvider jsonProvider = new JacksonJsonProvider();
        private final MappingProvider mappingProvider = new JacksonMappingProvider();
    
        @Override
        public JsonProvider jsonProvider() {
            return jsonProvider;
        }
    
        @Override
        public MappingProvider mappingProvider() {
            return mappingProvider;
        }
    
        @Override
        public Set<Option> options() {
            return EnumSet.noneOf(Option.class);
        }
    });
     
     

      9. Predicates过滤器断言

      有三种方式创建过滤器断言。

      9.1 Inline Predicates

      即使用过滤器断言表达式?(<@expression>),例如:

      List<Map<String, Object>> books =  JsonPath.parse(json)
                                           .read("$.store.book[?(@.price < 10)]");
       
       
      • 1
      • 2

      9.2 Filter Predicates

      使用Filter API。例如:

      import static com.jayway.jsonpath.JsonPath.parse;
      import static com.jayway.jsonpath.Criteria.where;
      import static com.jayway.jsonpath.Filter.filter;
      ...
      ...
      
      Filter cheapFictionFilter = filter(
         where("category").is("fiction").and("price").lte(10D)
      );
      
      List<Map<String, Object>> books =  
         parse(json).read("$.store.book[?]", cheapFictionFilter);
      
      Filter fooOrBar = filter(
         where("foo").exists(true)).or(where("bar").exists(true)
      );
      
      Filter fooAndBar = filter(
         where("foo").exists(true)).and(where("bar").exists(true)
      );
       
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      注意:

      • JsonPath表达式中必须要有断言占位符?,当有多个占位符时,会依据顺序进行替换。
      • 多个filter之间还可以使用or或and。

      9.3 Roll Your Own

      自己实现Predicate接口。

      Predicate booksWithISBN = new Predicate() {
          @Override
          public boolean apply(PredicateContext ctx) {
              return ctx.item(Map.class).containsKey("isbn");
          }
      };
      
      List<Map<String, Object>> books = 
         reader.read("$.store.book[?].isbn", List.class, booksWithISBN);
       
       

        10. 返回检索到的Path路径列表

        有时候需要返回当前JsonPath表达式所检索到的全部路径,可以如下使用:

        Configuration conf = Configuration.builder()
           .options(Option.AS_PATH_LIST).build();
        
        List<String> pathList = using(conf).parse(json).read("$..author");
        
        assertThat(pathList).containsExactly(
            "$['store']['book'][0]['author']",
            "$['store']['book'][1]['author']",
            "$['store']['book'][2]['author']",
            "$['store']['book'][3]['author']");
         
         

          11. 配置Options

          11.1 DEFAULT_PATH_LEAF_TO_NULL

          当检索不到时返回null对象,否则如果不配置这个,会直接抛出异常PathNotFoundException,例如:

          [
             {
                "name" : "john",
                "gender" : "male"
             },
             {
                "name" : "ben"
             }
          ]
           
           
            Configuration conf = Configuration.defaultConfiguration();
            
            //Works fine
            String gender0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
            //PathNotFoundException thrown
            String gender1 = JsonPath.using(conf).parse(json).read("$[1]['gender']");
            
            Configuration conf2 = conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);
            
            //Works fine
            String gender0 = JsonPath.using(conf2).parse(json).read("$[0]['gender']");
            //Works fine (null is returned)
            String gender1 = JsonPath.using(conf2).parse(json).read("$[1]['gender']");
             
             

              11.2 ALWAYS_RETURN_LIST

              总是返回list,即便是一个确定的非list类型,也会被包装成list。

              11.3 SUPPRESS_EXCEPTIONS

              不抛出异常,需要判断如下:

              • ALWAYS_RETURN_LIST开启,则返回空list
              • ALWAYS_RETURN_LIST关闭,则返回null

              11.4 AS_PATH_LIST

              返回path

              11.5 REQUIRE_PROPERTIES

              如果设置,则不允许使用通配符,比如$[*].b,会抛出PathNotFoundException异常。

              12. Cache SPI

              每次read时都会获取cache,以提高速度,但默认情况下是不启用的。

              @Override
              public <T> T read(String path, Predicate... filters) {
                  notEmpty(path, "path can not be null or empty");
                  Cache cache = CacheProvider.getCache();
                  path = path.trim();
                  LinkedList filterStack = new LinkedList<Predicate>(asList(filters));
                  String cacheKey = Utils.concat(path, filterStack.toString());
                  JsonPath jsonPath = cache.get(cacheKey);
                  if(jsonPath != null){
                      return read(jsonPath);
                  } else {
                      jsonPath = compile(path, filters);
                      cache.put(cacheKey, jsonPath);
                      return read(jsonPath);
                  }
              }
               
               

                JsonPath 2.1.0提供新的spi,必须在使用前或抛出JsonPathException前配置。目前提供了两种实现:

                • com.jayway.jsonpath.spi.cache.NOOPCache (no cache)
                • com.jayway.jsonpath.spi.cache.LRUCache (default, thread safe)

                如果想要自己实现,例如:

                CacheProvider.setCache(new Cache() {
                    //Not thread safe simple cache
                    private Map<String, JsonPath> map = new HashMap<String, JsonPath>();
                
                    @Override
                    public JsonPath get(String key) {
                        return map.get(key);
                    }
                
                    @Override
                    public void put(String key, JsonPath jsonPath) {
                        map.put(key, jsonPath);
                    }
                });
                 
                 
                • 1
                • 2
                • 3
                • 4
                • 5
                • 6
                • 7
                • 8
                • 9
                • 10
                • 11
                • 12
                • 13
                • 14

                参考:

                https://github.com/jayway/JsonPath JsonPath

                • JSONPath - 是xpath在json的应用。
                xml最大的优点就有大量的工具可以分析,转换,和选择性的提取文档中的数据。XPath是这些最强大的工具之一。
                如果可以使用xpath来解析json,以下的问题可以被解决:

                1,数据不使用特殊的脚本,可以在客户端交互的发现并取并获取。
                2,客户机请求的JSON数据可以减少到服务器上的相关部分,这样可以最大限度地减少服务器响应的带宽使用率。
                如果我们愿意,这个可以解析json数据的工具会变得有意义。随之而来的问题是它如何工作,jsonpath的表达式看起来怎么样。

                事实上,json是由c系统编程语言表示自然数据,有特定语言的特定语法来访问json数据。
                xpath的表达式:
                /store/book[1]/title
                我们可以看作是:
                x.store.book[0].title
                x['store']['book'][0]['title']

                在Javascript, Python 和 PHP 中一个变量x表示json数据。经过观察,特定的语言里有内置xpath来解析数据。

                JSONPath工具的问题

                -依赖某种特定的语言
                - 需要依赖XPath 1.0
                - 减少代码量和内存的消耗
                - 在运行时
                          



                • JSONPath 表达式

                JSONPath 是参照,xpath表达式来解析xml文档的方式,json数据结构通常是匿名的并且不一定需要有根元素。JSONPaht 用一个抽象的名字$来表示最外层对象。

                JOSNPath 表达式可以使用.  符号如下:

                $.store.book[0].title

                或者使用[] 符号

                $['store']['book'][0]['title']


                从输入路径来看。内部或者输出的路径都会转化成-符号。

                JSONPath 允许使用通配符 * 表示所以的子元素名和数组索引。还允许使用 '..' 从E4X参照过来的和数组切分语法 [start:end:step]是从ECMASCRIPT 4 参照过来的。

                表达式在下面的脚本语言中可以使用显示的名称或者索引:

                $.store.book[(@.length-1)].title


                使用'@'符号表示当前的对象,?(<判断表达式>) 使用逻辑表达式来过滤。

                $.store.book[?(@.price < 10)].title



                这里有个表格,说明JSONPath语法元素和对应XPath元素的对比。
                XPathJSONPathDescription
                /$表示根元素
                .@ 当前元素
                /. or []子元素
                ..n/a父元素
                //..递归下降,JSONPath是从E4X借鉴的。
                **通配符,表示所有的元素
                @n/a 属性访问字符
                [][]
                子元素操作符
                |[,]
                连接操作符在XPath 结果合并其它结点集合。JSONP允许name或者数组索引。
                n/a[start:end:step]
                数组分割操作从ES4借鉴。
                []?()
                应用过滤表示式
                n/a()
                脚本表达式,使用在脚本引擎下面。
                ()n/aXpath分组

                XPath还有很多的语法(本地路径,操作符,和函数)没有列在这里。只要知道xpath和jsonpath脚本之中的不同点就行了。
                • []在xpath表达式总是从前面的路径来操作数组,索引是从1开始。
                • 使用JOSNPath的[]操作符操作一个对象或者数组,索引是从0开始。



                • SONPath 例子

                接下我们看jsonpath表示的例子。下面是一个简单的json数据结构代表一个书店(原始的xml文件是)
                { "store": {
                    "book": [ 
                      { "category": "reference",
                        "author": "Nigel Rees",
                        "title": "Sayings of the Century",
                        "price": 8.95
                      },
                      { "category": "fiction",
                        "author": "Evelyn Waugh",
                        "title": "Sword of Honour",
                        "price": 12.99
                      },
                      { "category": "fiction",
                        "author": "Herman Melville",
                        "title": "Moby Dick",
                        "isbn": "0-553-21311-3",
                        "price": 8.99
                      },
                      { "category": "fiction",
                        "author": "J. R. R. Tolkien",
                        "title": "The Lord of the Rings",
                        "isbn": "0-395-19395-8",
                        "price": 22.99
                      }
                    ],
                    "bicycle": {
                      "color": "red",
                      "price": 19.95
                    }
                  }
                }
                
                XPathJSONPath结果
                /store/book/author$.store.book[*].author
                书点所有书的作者
                //author$..author
                所有的作者
                /store/*$.store.*
                store的所有元素。所有的bookst和bicycle
                /store//price$.store..price
                store里面所有东西的price
                //book[3]$..book[2]
                第三个书
                //book[last()]$..book[(@.length-1)]最后一本书
                //book[position()<3]$..book[0,1]
                $..book[:2]
                前面的两本书。
                //book[isbn]$..book[?(@.isbn)] 过滤出所有的包含isbn的书。
                //book[price<10]$..book[?(@.price<10)]过滤出价格低于10的书。
                //*$..*
                所有元素。

                JSONPath 表达式的使用


                一、JSONPath使用需要的包
                
                           
                           
                1. <dependency>
                2. <groupId>com.jayway.jsonpath</groupId>
                3. <artifactId>json-path</artifactId>
                4. <version> 2.4. 0</version>
                5. </dependency>
                二、使用说明

                1、JSONPath是xpath在json的应用
                2、JSONPath 是参照xpath表达式来解析xml文档的方式,json数据结构通常是匿名的并且不一定需要有根元素。
                3、JSONPath 用一个抽象的名字$来表示最外层对象
                4、JSONPath 允许使用通配符 * 表示所以的子元素名和数组索引

                三、JSONPath表达式语法

                JSONPath 表达式可以使用.符号解析json:
                $.store.book[0].title
                或者使用[]符号
                $['store']['book'][0]['title']

                四、测试实例

                Json文件内容如下:

                
                           
                           
                1. { "store": {
                2. "book": [
                3. { "category": "reference",
                4. "author": "Nigel Rees",
                5. "title": "Sayings of the Century",
                6. "price": 8.95
                7. },
                8. { "category": "fiction",
                9. "author": "Evelyn Waugh",
                10. "title": "Sword of Honour",
                11. "price": 12.99,
                12. "isbn": "0-553-21311-3"
                13. }
                14. ],
                15. "bicycle": {
                16. "color": "red",
                17. "price": 19.95
                18. }
                19. }
                20. }

                首先,读取json文件,使用commons.io的 FileUtils的readFileToString方法:

                
                           
                           
                1. String path = System.getProperty( "user.dir")+ File.separator+ "testdata"+ File.separator+ "test.json";
                2. String jsonString = FileUtils.readFileToString(new File(path), "utf-8");
                3. ReadContext context = JsonPath.parse(json);

                其次,输出book[1]的author值。有两种方法:
                方法一:

                
                           
                           
                1. JsonPath.read(json, "$.store.book[1].author");
                2. context.read( "$.store.book[1].author");
                3. 输出: Evelyn Waugh

                方法二:

                
                           
                           
                1. JsonPath.read(json, "$['store']['book'][1]['author']");
                2. context.read( "$['store']['book'][1]['author']");
                3. 输出: Evelyn Waugh

                //输出book[*]中category == 'reference'的book

                
                           
                           
                1. List< Object> categorys = context.read( "$.store.book[?(@.category == 'reference')]");
                2. for( Object st: categorys){
                3. System.out. println(st. toString());
                4. }
                5. 输出: {category=reference, author= Nigel Rees, title= Sayings of the Century, price= 8.95}

                //输出book[*]中price>10的book

                
                           
                           
                1. List< Object> prices = context.read( "$.store.book[?(@.price>10)]");
                2. for( Object p:prices){
                3. System.out. println(p. toString());
                4. }
                5. 输出:{category=fiction, author= Evelyn Waugh, title= Sword of Honour, price= 12.99, isbn= 0- 553- 21311- 3}

                //bicycle[*]中含有color元素的bicycle

                
                           
                           
                1. List< Object> color = context.read( "$.store.bicycle[?(@.color)]");
                2. for( Object is :color){
                3. System.out. println( is. toString());
                4. }
                5. 输出: //{color=red, price=19.95}

                //输出该json中所有price的值

                
                           
                           
                1. List< Object> pp = context.read( "$..price");
                2. for( Object p :pp){
                3. System.out. println(p. toString());
                4. }
                5. 输出: 8.95 12.99 19.95
                6. List< String> authors = context.read( "$.store.book[*].author");
                7. for ( String str : authors) {
                8. System.out. println(str);
                9. }
                10. 输出: Nigel Rees Evelyn Waugh
                五、XPATH 和 JSONPath获取元素的方法比较

                []在xpath表达式总是从前面的路径来操作数组,索引是从1开始。
                使用JOSNPath的[]操作符操作一个对象或者数组,索引是从0开始。


                //输出book[0]的author值
                        String author = JsonPath.read(json, "$.store.book[0].author");
                         System.out.println("author\t"+author);
                        //输出全部author的值,使用Iterator迭代
                        List<String> authors = JsonPath.read(json, "$.store.book[*].author");
                        System.out.println("authors\t"+authors);
                         
                        //输出book[*]中category == 'reference'的book
                        List<Object> books = JsonPath.read(json, "$.store.book[?(@.category == 'reference')]");               
                        System.out.println("books\t"+books);
                        //输出book[*]中category == 'reference'的book或者
                        List<Object> books2 = JsonPath.read(json, "$.store.book[?(@.category == 'reference' || @.price>10)]");               
                        System.out.println("books2\t"+books2);
                        
                        //输出book[*]中category == 'reference'的book的author 
                        List<Object> books1 = JsonPath.read(json, "$.store.book[?(@.category == 'reference')].author");               
                        System.out.println("books1\t"+books1);
                         
                        //输出book[*]中price>10的book
                        List<Object> b1 = JsonPath.read(json, "$.store.book[?(@.price>10)]");
                        System.out.println("b1"+b1);
                        
                        //输出book[*]中含有isbn元素的book
                        List<Object> b2 = JsonPath.read(json, "$.store.book[?(@.isbn)]");
                        System.out.println("b2"+b2);
                         
                        //输出该json中所有price的值
                        List<Double> prices = JsonPath.read(json, "$..price");
                        System.out.println("prices"+prices);
                        
                        //输出该json中所有title的值
                        List<Double> title = JsonPath.read(json, "$..title");
                        System.out.println("title"+title);
                        //输出该json中book 0,1的值
                        List<Double> book01 = JsonPath.read(json, "$..book[0,1]");
                        System.out.println("book01"+book01);
                       /* //输出该json中book 0,1的值
                        List<Double> book012 = JsonPath.read(json, "$..book[-2:]");
                        System.out.println("book012"+book012);*/
                         
                        //可以提前编辑一个路径,并多次使用它
                        JsonPath path = JsonPath.compile("$.store.book[*]"); 
                        List<Object> b3 = path.read(json); 
                        System.out.println("path\t"+path+"\n"+b3);

                • 1
                  点赞
                • 4
                  收藏
                  觉得还不错? 一键收藏
                • 0
                  评论
                评论
                添加红包

                请填写红包祝福语或标题

                红包个数最小为10个

                红包金额最低5元

                当前余额3.43前往充值 >
                需支付:10.00
                成就一亿技术人!
                领取后你会自动成为博主和红包主的粉丝 规则
                hope_wisdom
                发出的红包
                实付
                使用余额支付
                点击重新获取
                扫码支付
                钱包余额 0

                抵扣说明:

                1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                余额充值