| #!/usr/bin/env python |
| # |
| # Copyright (C) 2010 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| |
| # This module is used for version 2 of the Google Data APIs. |
| |
| |
| __author__ = 'j.s@google.com (Jeff Scudder)' |
| |
| |
| """Provides classes and methods for working with JSON-C. |
| |
| This module is experimental and subject to backwards incompatible changes. |
| |
| Jsonc: Class which represents JSON-C data and provides pythonic member |
| access which is a bit cleaner than working with plain old dicts. |
| parse_json: Converts a JSON-C string into a Jsonc object. |
| jsonc_to_string: Converts a Jsonc object into a string of JSON-C. |
| """ |
| |
| |
| try: |
| import simplejson |
| except ImportError: |
| try: |
| # Try to import from django, should work on App Engine |
| from django.utils import simplejson |
| except ImportError: |
| # Should work for Python2.6 and higher. |
| import json as simplejson |
| |
| |
| def _convert_to_jsonc(x): |
| """Builds a Jsonc objects which wraps the argument's members.""" |
| |
| if isinstance(x, dict): |
| jsonc_obj = Jsonc() |
| # Recursively transform all members of the dict. |
| # When converting a dict, we do not convert _name items into private |
| # Jsonc members. |
| for key, value in x.iteritems(): |
| jsonc_obj._dict[key] = _convert_to_jsonc(value) |
| return jsonc_obj |
| elif isinstance(x, list): |
| # Recursively transform all members of the list. |
| members = [] |
| for item in x: |
| members.append(_convert_to_jsonc(item)) |
| return members |
| else: |
| # Return the base object. |
| return x |
| |
| |
| def parse_json(json_string): |
| """Converts a JSON-C string into a Jsonc object. |
| |
| Args: |
| json_string: str or unicode The JSON to be parsed. |
| |
| Returns: |
| A new Jsonc object. |
| """ |
| |
| return _convert_to_jsonc(simplejson.loads(json_string)) |
| |
| |
| def parse_json_file(json_file): |
| return _convert_to_jsonc(simplejson.load(json_file)) |
| |
| |
| def jsonc_to_string(jsonc_obj): |
| """Converts a Jsonc object into a string of JSON-C.""" |
| |
| return simplejson.dumps(_convert_to_object(jsonc_obj)) |
| |
| |
| def prettify_jsonc(jsonc_obj, indentation=2): |
| """Converts a Jsonc object to a pretified (intented) JSON string.""" |
| |
| return simplejson.dumps(_convert_to_object(jsonc_obj), indent=indentation) |
| |
| |
| |
| def _convert_to_object(jsonc_obj): |
| """Creates a new dict or list which has the data in the Jsonc object. |
| |
| Used to convert the Jsonc object to a plain old Python object to simplify |
| conversion to a JSON-C string. |
| |
| Args: |
| jsonc_obj: A Jsonc object to be converted into simple Python objects |
| (dicts, lists, etc.) |
| |
| Returns: |
| Either a dict, list, or other object with members converted from Jsonc |
| objects to the corresponding simple Python object. |
| """ |
| |
| if isinstance(jsonc_obj, Jsonc): |
| plain = {} |
| for key, value in jsonc_obj._dict.iteritems(): |
| plain[key] = _convert_to_object(value) |
| return plain |
| elif isinstance(jsonc_obj, list): |
| plain = [] |
| for item in jsonc_obj: |
| plain.append(_convert_to_object(item)) |
| return plain |
| else: |
| return jsonc_obj |
| |
| |
| def _to_jsonc_name(member_name): |
| """Converts a Python style member name to a JSON-C style name. |
| |
| JSON-C uses camelCaseWithLower while Python tends to use |
| lower_with_underscores so this method converts as follows: |
| |
| spam becomes spam |
| spam_and_eggs becomes spamAndEggs |
| |
| Args: |
| member_name: str or unicode The Python syle name which should be |
| converted to JSON-C style. |
| |
| Returns: |
| The JSON-C style name as a str or unicode. |
| """ |
| |
| characters = [] |
| uppercase_next = False |
| for character in member_name: |
| if character == '_': |
| uppercase_next = True |
| elif uppercase_next: |
| characters.append(character.upper()) |
| uppercase_next = False |
| else: |
| characters.append(character) |
| return ''.join(characters) |
| |
| |
| class Jsonc(object): |
| """Represents JSON-C data in an easy to access object format. |
| |
| To access the members of a JSON structure which looks like this: |
| { |
| "data": { |
| "totalItems": 800, |
| "items": [ |
| { |
| "content": { |
| "1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp" |
| }, |
| "viewCount": 220101, |
| "commentCount": 22, |
| "favoriteCount": 201 |
| } |
| ] |
| }, |
| "apiVersion": "2.0" |
| } |
| |
| You would do the following: |
| x = gdata.core.parse_json(the_above_string) |
| # Gives you 800 |
| x.data.total_items |
| # Should be 22 |
| x.data.items[0].comment_count |
| # The apiVersion is '2.0' |
| x.api_version |
| |
| To create a Jsonc object which would produce the above JSON, you would do: |
| gdata.core.Jsonc( |
| api_version='2.0', |
| data=gdata.core.Jsonc( |
| total_items=800, |
| items=[ |
| gdata.core.Jsonc( |
| view_count=220101, |
| comment_count=22, |
| favorite_count=201, |
| content={ |
| '1': ('rtsp://v5.cache3.c.youtube.com' |
| '/CiILENy.../0/0/0/video.3gp')})])) |
| or |
| x = gdata.core.Jsonc() |
| x.api_version = '2.0' |
| x.data = gdata.core.Jsonc() |
| x.data.total_items = 800 |
| x.data.items = [] |
| # etc. |
| |
| How it works: |
| The JSON-C data is stored in an internal dictionary (._dict) and the |
| getattr, setattr, and delattr methods rewrite the name which you provide |
| to mirror the expected format in JSON-C. (For more details on name |
| conversion see _to_jsonc_name.) You may also access members using |
| getitem, setitem, delitem as you would for a dictionary. For example |
| x.data.total_items is equivalent to x['data']['totalItems'] |
| (Not all dict methods are supported so if you need something other than |
| the item operations, then you will want to use the ._dict member). |
| |
| You may need to use getitem or the _dict member to access certain |
| properties in cases where the JSON-C syntax does not map neatly to Python |
| objects. For example the YouTube Video feed has some JSON like this: |
| "content": {"1": "rtsp://v5.cache3.c.youtube.com..."...} |
| You cannot do x.content.1 in Python, so you would use the getitem as |
| follows: |
| x.content['1'] |
| or you could use the _dict member as follows: |
| x.content._dict['1'] |
| |
| If you need to create a new object with such a mapping you could use. |
| |
| x.content = gdata.core.Jsonc(_dict={'1': 'rtsp://cache3.c.youtube.com...'}) |
| """ |
| |
| def __init__(self, _dict=None, **kwargs): |
| json = _dict or {} |
| for key, value in kwargs.iteritems(): |
| if key.startswith('_'): |
| object.__setattr__(self, key, value) |
| else: |
| json[_to_jsonc_name(key)] = _convert_to_jsonc(value) |
| |
| object.__setattr__(self, '_dict', json) |
| |
| def __setattr__(self, name, value): |
| if name.startswith('_'): |
| object.__setattr__(self, name, value) |
| else: |
| object.__getattribute__( |
| self, '_dict')[_to_jsonc_name(name)] = _convert_to_jsonc(value) |
| |
| def __getattr__(self, name): |
| if name.startswith('_'): |
| object.__getattribute__(self, name) |
| else: |
| try: |
| return object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] |
| except KeyError: |
| raise AttributeError( |
| 'No member for %s or [\'%s\']' % (name, _to_jsonc_name(name))) |
| |
| |
| def __delattr__(self, name): |
| if name.startswith('_'): |
| object.__delattr__(self, name) |
| else: |
| try: |
| del object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] |
| except KeyError: |
| raise AttributeError( |
| 'No member for %s (or [\'%s\'])' % (name, _to_jsonc_name(name))) |
| |
| # For container methods pass-through to the underlying dict. |
| def __getitem__(self, key): |
| return self._dict[key] |
| |
| def __setitem__(self, key, value): |
| self._dict[key] = value |
| |
| def __delitem__(self, key): |
| del self._dict[key] |