├── .gitignore ├── JSON2Mantle ├── __init__.py ├── cli.py ├── objc_template.py ├── renderer.py ├── templates │ ├── model.h │ └── model.m └── utils.py ├── LICENSE ├── MANIFEST.in ├── README.md ├── setup.py └── tests ├── __init__.py └── top10.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ -------------------------------------------------------------------------------- /JSON2Mantle/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /JSON2Mantle/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | JSON2Mantle 4 | 5 | Generate Mantle models using JSON files. 6 | """ 7 | from __future__ import unicode_literals 8 | 9 | import argparse 10 | import json 11 | import os 12 | import re 13 | import sys 14 | import time 15 | import types 16 | 17 | 18 | import json2mantle.objc_template as objc_tpl 19 | import json2mantle.utils as utils 20 | from json2mantle.renderer import TemplateRenderer 21 | 22 | 23 | class JSON2Mantle(object): 24 | 25 | def __init__(self): 26 | self.class_prefix = '' 27 | self.class_suffix = 'Model' 28 | self.properties = {} 29 | self.meta_data = { 30 | 'year': time.strftime('%Y', time.gmtime()), 31 | 'created_at': time.strftime('%m/%d/%y', time.gmtime()), 32 | 'author': utils.get_current_user_name(), 33 | } 34 | self._version_workaround() 35 | # TODO: finish the reserved word list 36 | self.reserved_words = ('class', 'id', 'super', 'description') 37 | 38 | def _version_workaround(self): 39 | """Workarounds for Python 2 and Python 3 40 | Detecting str and unicode type 41 | Make `raw_input` as the alias of `input` in Python 3 42 | """ 43 | if sys.version_info[0] == 2: 44 | self.str_type = unicode 45 | self.input = raw_input 46 | elif sys.version_info[0] == 3: 47 | self.str_type = str 48 | self.input = input 49 | 50 | def make_class_name(self, name): 51 | """Generates Objective-C style class name. 52 | Format: PREFIX + ClassName + Suffix 53 | """ 54 | assert len(name) != 0 55 | return self.class_prefix + name[0].upper() + name[1:] + self.class_suffix 56 | 57 | def _convert_name_style(self, name): 58 | """Converts `var_name` to `varName` style. 59 | Moreover, rename those with reserved words 60 | """ 61 | if name in self.reserved_words: 62 | new_name = 'model{}{}'.format(name[0].upper(), name[1:]) 63 | return new_name 64 | candidates = re.findall(r'(_\w)', name) 65 | if not candidates: 66 | return name 67 | new_name = re.sub(r'_(\w)', lambda x: x.group(1).upper(), name) 68 | return new_name 69 | 70 | def get_template_data(self): 71 | """Generates template variables by using extracted properties. 72 | """ 73 | render_h = {} 74 | render_m = {} 75 | 76 | for model_name, properties in self.properties.items(): 77 | # header: properties 78 | joined_properties = '\n'.join( 79 | map(objc_tpl.property_tpl, properties)) 80 | 81 | # header: extra headers 82 | joined_headers = '\n'.join( 83 | filter(lambda x: x, map(objc_tpl.header_tpl, properties))) 84 | 85 | # implementation: aliases 86 | joined_aliases = '\n '.join( 87 | filter(lambda x: x, map(objc_tpl.alias_tpl, properties))) 88 | 89 | # implementation: transformers 90 | joined_transformers = '\n'.join( 91 | filter(lambda x: x, map(objc_tpl.transformer_tpl, properties))) 92 | 93 | render_h[model_name] = { 94 | 'file_name': model_name, 95 | 'properties': joined_properties, 96 | 'created_at': self.meta_data['created_at'], 97 | 'author': self.meta_data['author'], 98 | 'year': self.meta_data['year'], 99 | 'headers': joined_headers, 100 | } 101 | 102 | render_m[model_name] = { 103 | 'file_name': model_name, 104 | 'property_alias': joined_aliases, 105 | 'created_at': self.meta_data['created_at'], 106 | 'author': self.meta_data['author'], 107 | 'year': self.meta_data['year'], 108 | 'transformers': joined_transformers, 109 | } 110 | 111 | return (render_h, render_m) 112 | 113 | def trim_class_name(self, class_name): 114 | """If the class name is not with prefix, add it 115 | """ 116 | if not class_name.startswith(self.class_prefix) or \ 117 | not class_name.endswith(self.class_suffix): 118 | class_name = self.make_class_name(class_name) 119 | return class_name 120 | 121 | def extract_properties(self, dict_data, class_name): 122 | """Extracts properties from a dictionary. 123 | This method is a recursive one, making nested sub-dictionary merged. 124 | """ 125 | # items: current properties 126 | items = [] 127 | # results: key = class_name, value = items 128 | results = {} 129 | # sub_model: sub model to be merged 130 | sub_model = {} 131 | 132 | class_name = self.trim_class_name(class_name) 133 | print('Generating "{}" file'.format(class_name)) 134 | 135 | for original_name, value in dict_data.items(): 136 | new_name = self._convert_name_style(original_name) 137 | 138 | if isinstance(value, dict): 139 | new_class_name = self.make_class_name(new_name) 140 | sub_model = self.extract_properties(value, new_class_name) 141 | results.update(sub_model) 142 | 143 | item = { 144 | 'name': new_name, 145 | 'original_name': original_name, 146 | 'storage': 'strong', 147 | 'class_name': new_class_name, 148 | 'transform': { 149 | 'type': 'Dictionary', 150 | 'class': new_class_name, 151 | }, 152 | } 153 | elif isinstance(value, list): 154 | new_class_name = self.make_class_name(new_name) 155 | if len(value) == 0: 156 | print('WARNING: "{}" is not generated'.format(new_class_name)) 157 | continue 158 | if isinstance(value[0], dict): 159 | sub_model = self.extract_properties(value[0], new_class_name) 160 | results.update(sub_model) 161 | item = { 162 | 'name': new_name, 163 | 'original_name': original_name, 164 | 'storage': 'strong', 165 | 'class_name': 'NSArray', 166 | 'transform': { 167 | 'type': 'Array', 168 | 'class': new_class_name, 169 | } 170 | } 171 | elif isinstance(value, self.str_type): 172 | item = { 173 | 'name': new_name, 174 | 'original_name': original_name, 175 | 'storage': 'copy', 176 | 'class_name': 'NSString', 177 | 'transform': None, 178 | } 179 | elif isinstance(value, int): 180 | item = { 181 | 'name': new_name, 182 | 'original_name': original_name, 183 | 'storage': 'assign', 184 | 'class_name': 'NSInteger', 185 | 'transform': None, 186 | } 187 | elif isinstance(value, bool): 188 | item = { 189 | 'name': new_name, 190 | 'original_name': original_name, 191 | 'storage': 'assign', 192 | 'class_name': 'BOOL', 193 | 'transform': None, 194 | } 195 | elif isinstance(value, float): 196 | item = { 197 | 'name': new_name, 198 | 'original_name': original_name, 199 | 'storage': 'assign', 200 | 'class_name': 'CGFloat', 201 | 'transform': None, 202 | } 203 | elif isinstance(value, types.NoneType): 204 | print('WARNING: "{}" is NULL'.format(original_name)) 205 | else: 206 | raise ValueError(value) 207 | 208 | items.append(item) 209 | 210 | results[class_name] = items 211 | # reduce 212 | results.update(sub_model) 213 | return results 214 | 215 | def generate_properties(self, dict_data, class_name): 216 | """Generates properties by given JSON, supporting nested structure. 217 | """ 218 | if isinstance(dict_data, list): 219 | class_name = self.input( 220 | '"{}" is an array, give the items a name: '.format( 221 | class_name)) 222 | dict_data = dict_data[0] 223 | self.properties = self.extract_properties(dict_data, class_name) 224 | 225 | 226 | def init_args(): 227 | parser = argparse.ArgumentParser( 228 | description='Generate Mantle models by a given JSON file.' 229 | ) 230 | parser.add_argument('json_file', 231 | help='the JSON file to be parsed') 232 | parser.add_argument('output_dir', 233 | help='output directory for generated Objective-C files') 234 | parser.add_argument('--prefix', 235 | help='class prefix of Objective-C files') 236 | parser.add_argument('--author', 237 | help='author info') 238 | args = parser.parse_args() 239 | 240 | if not os.path.exists(args.output_dir): 241 | try: 242 | os.mkdir(args.output_dir) 243 | except IOError: 244 | print('Error: could not create directory {}'.format( 245 | args.output_dir 246 | )) 247 | exit() 248 | 249 | return args 250 | 251 | 252 | def main(): 253 | """ Main function 254 | """ 255 | args = init_args() 256 | 257 | try: 258 | dict_data = json.loads(open(args.json_file).read()) 259 | except IOError: 260 | print('Error: no such file {}'.format(args.json_file)) 261 | exit() 262 | 263 | j2m = JSON2Mantle() 264 | 265 | # Gets meta data 266 | j2m.class_prefix = args.prefix if args.prefix else '' 267 | if args.author: 268 | j2m.meta_data['author'] = args.author 269 | 270 | # Get the file base name 271 | file_basename = os.path.basename(args.json_file) 272 | 273 | # Eliminating filename extension 274 | class_name = file_basename.split('.')[0] 275 | 276 | j2m.generate_properties(dict_data, class_name) 277 | 278 | # .h and .m data for rendering 279 | render_h, render_m = j2m.get_template_data() 280 | 281 | template_renderer = TemplateRenderer(render_h, render_m, args.output_dir) 282 | template_renderer.render() 283 | 284 | if __name__ == '__main__': 285 | main() 286 | -------------------------------------------------------------------------------- /JSON2Mantle/objc_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | JSON2Mantle 3 | 4 | Objective-C templates 5 | """ 6 | 7 | import re 8 | 9 | 10 | def header_tpl(data): 11 | """Generates header file to import 12 | Output: 13 | #import "XYZUserInfo.h" 14 | """ 15 | if not data['transform']: 16 | return None 17 | if data['class_name'] == 'NSArray': 18 | name = data['transform']['class'] 19 | else: 20 | name = data['class_name'] 21 | result = '#import "{}.h"'.format(name) 22 | return result 23 | 24 | 25 | def property_tpl(data): 26 | """Generates variable data, according to name, type, storage. 27 | Output: 28 | @property (nonatomic, copy) NSString *testString; 29 | """ 30 | name = data['name'] if data[ 31 | 'storage'] == 'assign' else '*{}'.format(data['name']) 32 | result = '@property (nonatomic, {}) {} {};'.format( 33 | data['storage'], 34 | data['class_name'], 35 | name) 36 | return result 37 | 38 | 39 | def alias_tpl(data): 40 | """Generates Mantle alias 41 | Output: 42 | @"postTime": @"post_time", 43 | """ 44 | if data['name'] == data['original_name']: 45 | return None 46 | name = data['original_name'] 47 | candidates = re.findall(r'(_\w)', name) 48 | if not candidates: 49 | new_name = data['name'] 50 | else: 51 | new_name = re.sub(r'_(\w)', lambda x: x.group(1).upper(), name) 52 | return '@"{}": @"{}",'.format(new_name, name) 53 | 54 | 55 | def transformer_tpl(data): 56 | """Generates Mantle transformer 57 | Output: 58 | 59 | /** 60 | * Converts '{}' property from '{}' class type. 61 | * 62 | * @return NSValueTransformer 63 | */ 64 | + (NSValueTransformer *)articleJSONTransformer 65 | { 66 | return [NSValueTransformer 67 | mtl_JSONArrayTransformerWithModelClass:XYZArticleModel.class]; 68 | } 69 | 70 | """ 71 | if not data['transform']: 72 | return None 73 | string = """ 74 | /** 75 | * Converts '{property}' property from '{class_name}' class. 76 | * 77 | * @return NSValueTransformer 78 | */ 79 | + (NSValueTransformer *){property}JSONTransformer 80 | {{ 81 | return [NSValueTransformer mtl_JSON{type}TransformerWithModelClass:{class_name}.class]; 82 | }}""".format( 83 | property=data['name'], 84 | type=data['transform']['type'], 85 | class_name=data['transform']['class'], 86 | ) 87 | return string 88 | -------------------------------------------------------------------------------- /JSON2Mantle/renderer.py: -------------------------------------------------------------------------------- 1 | """ 2 | JSON2Mantle 3 | 4 | Renderer 5 | """ 6 | import re 7 | import os 8 | 9 | class TemplateRenderer(object): 10 | """Template Renderer 11 | """ 12 | 13 | def __init__(self, properties_h, properties_m, output_dir='output'): 14 | basepath = os.path.dirname(__file__) 15 | h_file = os.path.abspath(os.path.join(basepath, 'templates', 'model.h')) 16 | m_file = os.path.abspath(os.path.join(basepath, 'templates', 'model.m')) 17 | self.properties = { 18 | 'h': properties_h, 19 | 'm': properties_m, 20 | } 21 | self.templates = { 22 | 'h': open(h_file).read(), 23 | 'm': open(m_file).read(), 24 | } 25 | self.output_dir = output_dir 26 | 27 | def render(self): 28 | """Renders template file with given data 29 | """ 30 | for model in ('h', 'm'): 31 | for class_name, prop in self.properties[model].items(): 32 | filename = '{}.{}'.format(class_name, model) 33 | output_file = os.path.join(self.output_dir, filename) 34 | output_doc = self.templates[model] 35 | 36 | for name, value in prop.items(): 37 | placeholder = '{{%s}}' % (name,) 38 | output_doc = output_doc.replace(placeholder, value) 39 | 40 | # clean up 41 | output_doc = re.sub(r'{{.*?}}', '', output_doc) 42 | 43 | with open(output_file, 'w') as output: 44 | output.write(output_doc) 45 | -------------------------------------------------------------------------------- /JSON2Mantle/templates/model.h: -------------------------------------------------------------------------------- 1 | // 2 | // {{file_name}}.h 3 | // 4 | // Created by JSON2Mantle on {{created_at}}. 5 | // Copyright (c) {{year}} {{author}}. All rights reserved. 6 | // 7 | 8 | #import 9 | {{headers}} 10 | 11 | /** 12 | * The {{file_name}} class represents a Mantle model. 13 | */ 14 | @interface {{file_name}} : MTLModel 15 | 16 | {{properties}} 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /JSON2Mantle/templates/model.m: -------------------------------------------------------------------------------- 1 | // 2 | // {{file_name}}.m 3 | // 4 | // Created by JSON2Mantle on {{created_at}}. 5 | // Copyright (c) {{year}} {{author}}. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "{{file_name}}.h" 10 | 11 | @implementation {{file_name}} 12 | 13 | /** 14 | * The dictionary returned by this method specifies 15 | * how your model object's properties map to the keys 16 | * in the JSON representation. 17 | * 18 | * @see https://github.com/Mantle/Mantle#jsonkeypathsbypropertykey 19 | * @return NSDictionary 20 | */ 21 | + (NSDictionary *)JSONKeyPathsByPropertyKey 22 | { 23 | // modelProperty : json_field_name 24 | return @{ 25 | {{property_alias}} 26 | }; 27 | } 28 | 29 | {{transformers}} 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /JSON2Mantle/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | JSON2Mantle 3 | 4 | Utilities 5 | """ 6 | 7 | from __future__ import unicode_literals 8 | 9 | import os 10 | import pwd 11 | 12 | mock_user_name = None 13 | 14 | # only works with Python 2 on OS X 15 | try: 16 | import objc 17 | import AddressBook as ab 18 | except ImportError: 19 | print('Could not import OS X Address Book, using "uid" instead') 20 | try: 21 | mock_user_name = pwd.getpwuid(os.getuid())[0] 22 | except: 23 | mock_user_name = '' 24 | 25 | 26 | # Note: following is adapted from https://gist.github.com/pklaus/1029870 27 | 28 | 29 | def pythonize(objc_obj): 30 | if isinstance(objc_obj, objc.pyobjc_unicode): 31 | return unicode(objc_obj) 32 | elif isinstance(objc_obj, ab.NSDate): 33 | return objc_obj.description() 34 | elif isinstance(objc_obj, ab.NSCFDictionary): 35 | # implicitly assuming keys are strings... 36 | return {k.lower(): pythonize(objc_obj[k]) 37 | for k in objc_obj.keys()} 38 | elif isinstance(objc_obj, ab.ABMultiValueCoreDataWrapper): 39 | return [pythonize(objc_obj.valueAtIndex_(index)) 40 | for index in range(0, objc_obj.count())] 41 | 42 | 43 | def ab_person_to_dict(person, skip=None): 44 | _default_skip_properties = frozenset(("com.apple.ABPersonMeProperty", 45 | "com.apple.ABImageData")) 46 | skip = _default_skip_properties if skip is None else frozenset(skip) 47 | props = person.allProperties() 48 | return {prop.lower(): pythonize(person.valueForProperty_(prop)) 49 | for prop in props if prop not in skip} 50 | 51 | 52 | def get_current_user_name(): 53 | """Gets the current user's name from Address Book 54 | """ 55 | # if fails to import AddressBook, returns empty name 56 | if mock_user_name: 57 | return mock_user_name 58 | address_book = ab.ABAddressBook.sharedAddressBook() 59 | try: 60 | me_unique = address_book.meUniqueId() 61 | except: 62 | me_unique = None 63 | people = address_book.people() 64 | all_contacts = [ab_person_to_dict(person) for person in people] 65 | 66 | user_name = '' 67 | for c in all_contacts: 68 | if c['uid'] == me_unique: 69 | try: 70 | user_name = '{} {}'.format( 71 | c['firstphonetic'], c['lastphonetic']) 72 | except: 73 | user_name = '' 74 | 75 | return user_name 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Xin Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include json2mantle/templates/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON2Mantle 2 | ======================== 3 | 4 | Generate [Mantle](https://github.com/Mantle/Mantle) models using JSON files. 5 | 6 | ##Quick start 7 | 8 | ### Install 9 | 10 | ``` 11 | $ sudo pip install JSON2Mantle 12 | ``` 13 | 14 | ### Usage 15 | 16 | ``` 17 | $ json2mantle [-h] [--prefix PREFIX] [--author AUTHOR] 18 | json_file output_dir 19 | ``` 20 | 21 | ### Example 22 | 23 | ``` 24 | $ json2mantle api_model.json class --prefix XYZ --author "John Smith" 25 | ``` 26 | 27 | will generate Mantle models according to your `api_model.json` structure. The output files will be created under `output_dir` directory, the author name will be `John Smith`, and the Objective-C classes have the prefix `XYZ`. 28 | 29 | ## Features 30 | 31 | * Supports nested JSON data, which means JSON2Mantle can generate the correct number of classes that the JSON file contains. 32 | * Convert field name like `var_name` to `varName` automatically. 33 | * Author name is grabbed from Address Book by default (Python 2 on OS X only). 34 | * Python 2/3 compatible. 35 | * Generated Objective-C files are documented. 36 | 37 | ## Note 38 | * **NOT** applicable for Mantle 2.0 yet. 39 | * When reserved words in Objective-C appear, it will replace the original name with a prefix `model`. For instance, if you have a field with the name `id`, the generated one would be `modelId`. 40 | * Only supports generating Objective-C files. 41 | * If the input JSON file is an array, it will ask you to give a name for the array items. 42 | 43 | ## TODO 44 | - [ ] reserved words 45 | - [ ] URL type 46 | - [ ] Unit test 47 | 48 | ## License 49 | The MIT License (MIT) 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys 3 | import os 4 | 5 | version = '0.0.9' 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | with open(os.path.join(here, 'README.md')) as f: 10 | readme = f.read() 11 | 12 | 13 | setup( 14 | name='JSON2Mantle', 15 | version=version, 16 | description='Generate Mantle models using JSON files', 17 | long_description=readme, 18 | author='Xin Wang', 19 | author_email='i@wangx.in', 20 | url='https://github.com/sutar/JSON2Mantle', 21 | license='MIT', 22 | classifiers=[ 23 | 'Development Status :: 4 - Beta', 24 | 'License :: OSI Approved :: MIT License', 25 | 'Intended Audience :: Developers', 26 | 'Programming Language :: Python :: 2.7', 27 | 'Programming Language :: Python :: 3.4', 28 | 'Programming Language :: Objective C', 29 | 'Topic :: Text Processing', 30 | ], 31 | keywords='mantle parser objective-c', 32 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 33 | include_package_data=True, 34 | zip_safe=False, 35 | install_requires=[ 36 | # -*- Extra requirements: -*- 37 | ], 38 | entry_points={ 39 | "console_scripts": [ 40 | "json2mantle = json2mantle.cli:main", 41 | ] 42 | }, 43 | ) 44 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __version__ = "0.0.1" 5 | -------------------------------------------------------------------------------- /tests/top10.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topten", 3 | "title": "十大热门话题", 4 | "time": 1423859352, 5 | "article": [{ 6 | "id": 1628703, 7 | "group_id": 1628703, 8 | "reply_id": 1628703, 9 | "flag": "", 10 | "position": 0, 11 | "is_top": false, 12 | "is_subject": true, 13 | "has_attachment": true, 14 | "is_admin": false, 15 | "title": "全宿舍都是单身不能忍!狗也想过节~(45)", 16 | "user": { 17 | "id": "piggydang", 18 | "user_name": "喵叽大人人贱不拆", 19 | "face_url": "http://static.byr.cn/uploadFace/P/piggydang.752.jpg", 20 | "face_width": 120, 21 | "face_height": 120, 22 | "gender": "f", 23 | "astro": "金牛座", 24 | "life": 135, 25 | "qq": "", 26 | "msn": "", 27 | "home_page": "", 28 | "level": "用户", 29 | "is_online": false, 30 | "post_count": 1498, 31 | "last_login_time": 1423848889, 32 | "last_login_ip": "101.254.163.*", 33 | "is_hide": false, 34 | "is_register": true, 35 | "score": 6893 36 | }, 37 | "post_time": 1423836810, 38 | "board_name": "Friends", 39 | "reply_count": 169, 40 | "last_reply_user_id": "pplove521hf", 41 | "last_reply_time": 1423854116, 42 | "id_count": "45" 43 | }, { 44 | "id": 5668886, 45 | "group_id": 5668886, 46 | "reply_id": 5668886, 47 | "flag": "", 48 | "position": 0, 49 | "is_top": false, 50 | "is_subject": true, 51 | "has_attachment": false, 52 | "is_admin": false, 53 | "title": "当你看到演员表中有谁时,会立马断定这是个烂片?(20)", 54 | "user": { 55 | "id": "alicegreen", 56 | "user_name": "一碗红尘", 57 | "face_url": "http://static.byr.cn/uploadFace/A/alicegreen.3682.jpg", 58 | "face_width": 120, 59 | "face_height": 80, 60 | "gender": "n", 61 | "astro": "", 62 | "life": 365, 63 | "qq": "", 64 | "msn": "", 65 | "home_page": "", 66 | "level": "用户", 67 | "is_online": false, 68 | "post_count": 10383, 69 | "last_login_time": 1423847623, 70 | "last_login_ip": "125.82.200.*", 71 | "is_hide": true, 72 | "is_register": true, 73 | "score": 11230 74 | }, 75 | "post_time": 1423844134, 76 | "board_name": "Talking", 77 | "reply_count": 21, 78 | "last_reply_user_id": "wmtws9dsj", 79 | "last_reply_time": 1423856813, 80 | "id_count": "20" 81 | }, { 82 | "id": 2732850, 83 | "group_id": 2732850, 84 | "reply_id": 2732850, 85 | "flag": "", 86 | "position": 0, 87 | "is_top": false, 88 | "is_subject": true, 89 | "has_attachment": false, 90 | "is_admin": false, 91 | "title": "如果刚才突然有个人跟你表白。。。(14)", 92 | "user": { 93 | "id": "luyang1992", 94 | "user_name": "baoziooxx", 95 | "face_url": "http://static.byr.cn/uploadFace/L/luyang1992.8535.jpg", 96 | "face_width": 118, 97 | "face_height": 120, 98 | "gender": "m", 99 | "astro": "处女座", 100 | "life": 365, 101 | "qq": "975825038", 102 | "msn": "", 103 | "home_page": "", 104 | "level": "用户", 105 | "is_online": false, 106 | "post_count": 57, 107 | "last_login_time": 1423843549, 108 | "last_login_ip": "123.118.5.*", 109 | "is_hide": false, 110 | "is_register": true, 111 | "score": 376 112 | }, 113 | "post_time": 1423843908, 114 | "board_name": "Feeling", 115 | "reply_count": 14, 116 | "last_reply_user_id": "whimly", 117 | "last_reply_time": 1423852547, 118 | "id_count": "14" 119 | }, { 120 | "id": 92522, 121 | "group_id": 92522, 122 | "reply_id": 92522, 123 | "flag": "g", 124 | "position": 0, 125 | "is_top": false, 126 | "is_subject": true, 127 | "has_attachment": true, 128 | "is_admin": false, 129 | "title": "【情歌王】大合唱正在合成中!(10)", 130 | "user": { 131 | "id": "Nicexm", 132 | "user_name": "【爱玩不完】狼人|MONKEY·D·LUFFY", 133 | "face_url": "http://static.byr.cn/uploadFace/N/Nicexm.5190.gif", 134 | "face_width": 120, 135 | "face_height": 120, 136 | "gender": "m", 137 | "astro": "狮子座", 138 | "life": 365, 139 | "qq": "", 140 | "msn": "", 141 | "home_page": "", 142 | "level": "用户", 143 | "is_online": false, 144 | "post_count": 133, 145 | "last_login_time": 1423847030, 146 | "last_login_ip": "183.249.88.*", 147 | "is_hide": false, 148 | "is_register": true, 149 | "score": 1546 150 | }, 151 | "post_time": 1423587169, 152 | "board_name": "KaraOK", 153 | "reply_count": 65, 154 | "last_reply_user_id": "yang199021", 155 | "last_reply_time": 1423850323, 156 | "id_count": "10" 157 | }, { 158 | "id": 159436, 159 | "group_id": 159436, 160 | "reply_id": 159436, 161 | "flag": "", 162 | "position": 0, 163 | "is_top": false, 164 | "is_subject": true, 165 | "has_attachment": false, 166 | "is_admin": false, 167 | "title": "萌妹子换笔记本,求推荐(6)", 168 | "user": { 169 | "id": "AdamWang", 170 | "user_name": "Adam", 171 | "face_url": "http://static.byr.cn/img/face_default_m.jpg", 172 | "face_width": 0, 173 | "face_height": 0, 174 | "gender": "m", 175 | "astro": "巨蟹座", 176 | "life": 365, 177 | "qq": "", 178 | "msn": "", 179 | "home_page": "", 180 | "level": "用户", 181 | "is_online": false, 182 | "post_count": 17, 183 | "last_login_time": 1423837422, 184 | "last_login_ip": "139.208.223.*", 185 | "is_hide": false, 186 | "is_register": true, 187 | "score": 334 188 | }, 189 | "post_time": 1423838399, 190 | "board_name": "Notebook", 191 | "reply_count": 7, 192 | "last_reply_user_id": "toobee", 193 | "last_reply_time": 1423851488, 194 | "id_count": "6" 195 | }, { 196 | "id": 974510, 197 | "group_id": 974510, 198 | "reply_id": 974510, 199 | "flag": "", 200 | "position": 0, 201 | "is_top": false, 202 | "is_subject": true, 203 | "has_attachment": false, 204 | "is_admin": false, 205 | "title": "[问题]关于考研好愁啊………跪请大侠分析(6)", 206 | "user": { 207 | "id": "xiaodong", 208 | "user_name": "xiaodong", 209 | "face_url": "http://static.byr.cn/img/face_default_m.jpg", 210 | "face_width": 0, 211 | "face_height": 0, 212 | "gender": "m", 213 | "astro": "巨蟹座", 214 | "life": 135, 215 | "qq": "", 216 | "msn": "", 217 | "home_page": "", 218 | "level": "用户", 219 | "is_online": false, 220 | "post_count": 5, 221 | "last_login_time": 1423838673, 222 | "last_login_ip": "117.136.3.*", 223 | "is_hide": false, 224 | "is_register": true, 225 | "score": 111 226 | }, 227 | "post_time": 1423820559, 228 | "board_name": "AimGraduate", 229 | "reply_count": 21, 230 | "last_reply_user_id": "toobee", 231 | "last_reply_time": 1423847797, 232 | "id_count": "6" 233 | }, { 234 | "id": 133982, 235 | "group_id": 133982, 236 | "reply_id": 133982, 237 | "flag": "", 238 | "position": 0, 239 | "is_top": false, 240 | "is_subject": true, 241 | "has_attachment": true, 242 | "is_admin": false, 243 | "title": "有了他在我以后吃不了午饭了(3)", 244 | "user": { 245 | "id": "Mylovehebe", 246 | "user_name": "Mylovehebe", 247 | "face_url": "http://static.byr.cn/uploadFace/M/Mylovehebe.8315.jpg", 248 | "face_width": 120, 249 | "face_height": 90, 250 | "gender": "m", 251 | "astro": "魔羯座", 252 | "life": 365, 253 | "qq": "", 254 | "msn": "", 255 | "home_page": "", 256 | "level": "用户", 257 | "is_online": false, 258 | "post_count": 241, 259 | "last_login_time": 1423805233, 260 | "last_login_ip": "123.116.158.*", 261 | "is_hide": false, 262 | "is_register": true, 263 | "score": 2275 264 | }, 265 | "post_time": 1423759398, 266 | "board_name": "Pet", 267 | "reply_count": 72, 268 | "last_reply_user_id": "Mylovehebe", 269 | "last_reply_time": 1423846937, 270 | "id_count": "3" 271 | }, { 272 | "id": 1698036, 273 | "group_id": 1698036, 274 | "reply_id": 1698036, 275 | "flag": "", 276 | "position": 0, 277 | "is_top": false, 278 | "is_subject": true, 279 | "has_attachment": false, 280 | "is_admin": false, 281 | "title": "想留北京了(3)", 282 | "user": { 283 | "id": "ppzhoujun", 284 | "user_name": "pptao", 285 | "face_url": "http://static.byr.cn/uploadFace/P/ppzhoujun.6033.jpg", 286 | "face_width": 119, 287 | "face_height": 120, 288 | "gender": "m", 289 | "astro": "白羊座", 290 | "life": 365, 291 | "qq": "", 292 | "msn": "", 293 | "home_page": "", 294 | "level": "用户", 295 | "is_online": false, 296 | "post_count": 337, 297 | "last_login_time": 1423835874, 298 | "last_login_ip": "223.104.21.*", 299 | "is_hide": false, 300 | "is_register": true, 301 | "score": 3061 302 | }, 303 | "post_time": 1423763736, 304 | "board_name": "Job", 305 | "reply_count": 115, 306 | "last_reply_user_id": "jokenliv", 307 | "last_reply_time": 1423846815, 308 | "id_count": "3" 309 | }, { 310 | "id": 982596, 311 | "group_id": 982596, 312 | "reply_id": 982596, 313 | "flag": "", 314 | "position": 0, 315 | "is_top": false, 316 | "is_subject": true, 317 | "has_attachment": false, 318 | "is_admin": false, 319 | "title": "求五六年后就业分析,弟弟高考面临选专业(3)", 320 | "user": { 321 | "id": "hihei", 322 | "user_name": "kitty", 323 | "face_url": "http://static.byr.cn/uploadFace/H/hihei.7467.jpg", 324 | "face_width": 110, 325 | "face_height": 120, 326 | "gender": "f", 327 | "astro": "双鱼座", 328 | "life": 365, 329 | "qq": "", 330 | "msn": "", 331 | "home_page": "", 332 | "level": "用户", 333 | "is_online": false, 334 | "post_count": 127, 335 | "last_login_time": 1423831780, 336 | "last_login_ip": "223.104.3.*", 337 | "is_hide": false, 338 | "is_register": true, 339 | "score": 6281 340 | }, 341 | "post_time": 1423788124, 342 | "board_name": "WorkLife", 343 | "reply_count": 130, 344 | "last_reply_user_id": "yang199021", 345 | "last_reply_time": 1423850215, 346 | "id_count": "3" 347 | }, { 348 | "id": 90663, 349 | "group_id": 90663, 350 | "reply_id": 90663, 351 | "flag": "", 352 | "position": 0, 353 | "is_top": false, 354 | "is_subject": true, 355 | "has_attachment": true, 356 | "is_admin": false, 357 | "title": "(干货更新)看完我是歌手现场!(3)", 358 | "user": { 359 | "id": "panda007", 360 | "user_name": "哈哈panda", 361 | "face_url": "http://static.byr.cn/uploadFace/P/panda007.58.jpg", 362 | "face_width": 88, 363 | "face_height": 120, 364 | "gender": "m", 365 | "astro": "处女座", 366 | "life": 365, 367 | "qq": "", 368 | "msn": "", 369 | "home_page": "", 370 | "level": "用户", 371 | "is_online": false, 372 | "post_count": 730, 373 | "last_login_time": 1423850159, 374 | "last_login_ip": "119.39.248.*", 375 | "is_hide": false, 376 | "is_register": true, 377 | "score": 7097 378 | }, 379 | "post_time": 1423759194, 380 | "board_name": "SuperStar", 381 | "reply_count": 80, 382 | "last_reply_user_id": "tingpink", 383 | "last_reply_time": 1423844083, 384 | "id_count": "3" 385 | }] 386 | } --------------------------------------------------------------------------------