blob: 0661ec6a0a918cbd7d6099c98c066b601a7cf6b6 [file] [log] [blame]
#!/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]