JSON และ Core Data บน iPhone


93

ฉันมีกราฟออบเจ็กต์ข้อมูลหลัก (ประกอบด้วยสองเอนทิตีที่เชื่อมโยงด้วยความสัมพันธ์ต่อกลุ่ม)

ฉันอยากรู้อยากเห็นในฐานะผู้พัฒนา iPhone ที่ค่อนข้างไม่มีประสบการณ์ไม่ว่าใครจะสามารถแนะนำแนวทางและการใช้งาน JSON ที่เหมาะสมสำหรับ iPhone ซึ่งจะทำให้ฉันสามารถ

  1. แปลงระเบียนข้อมูลหลักเป็นสตริง JSON (ในขณะที่รักษาความสัมพันธ์ระหว่างเอนทิตี) และ

  2. แปลงสตริง JSON กลับเป็นวัตถุข้อมูลหลัก (รักษาความสัมพันธ์ระหว่างเอนทิตีอีกครั้ง)

ฉันได้ค้นหาตัวอย่างบทช่วยสอน / โค้ดไม่สำเร็จในประเด็นนี้ดังนั้นเราจะได้รับความช่วยเหลือเป็นอย่างดี


3
สำหรับใครที่กำลังมองหา iOS5 ตอนนี้มีNSJSONSerialization developer.apple.com/library/mac/#documentation/Foundation/… stackoverflow.com/questions/6726899/nsjsonserialization-in-ios5
nicerobot

ฉันรู้ว่าคำถามนี้เก่าไปหน่อย แต่ฉันได้สร้างไลบรารีง่ายๆที่ชื่อว่าOSReflectionKitซึ่งช่วยให้คุณสามารถทำให้เป็นอนุกรม / deserialize วัตถุเป็น / จาก JSON โดยใช้NSJSONSerializationหรือ NSDictionary นอกจากนี้ยังสนับสนุนวัตถุข้อมูลหลัก
Alexandre OS

คำตอบ:


103

ก่อนอื่นให้เลือกไลบรารี JSON ที่จะใช้โดยส่วนตัวแล้วฉันชอบ TouchJSON แต่อีกหลาย ๆ คนก็ค่อนข้างดีเช่นกัน ส่วนที่ซับซ้อนแม้ว่าจะไม่ยากมาก แต่ก็คือการแปลงวัตถุที่มีการจัดการของคุณให้เป็นโครงสร้างที่เหมาะสมสำหรับการแปลง ฉันเขียนข้อความนี้อย่างรวดเร็วดังนั้นมันอาจมีข้อผิดพลาดหรือสองข้อ :)

วิธีการที่คุณเรียกคือ:

- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects;
- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc;

และการดำเนินการมีดังนี้:

- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
  NSDictionary *attributesByName = [[managedObject entity] attributesByName];
  NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
  NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
  [valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];
  for (NSString *relationshipName in [relationshipsByName allKeys]) {
    NSRelationshipDescription *description = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];
    if (![description isToMany]) {
      NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
      [valuesDictionary setObject:[self dataStructureForManagedObject:relationshipObject] forKey:relationshipName];
      continue;
    }
    NSSet *relationshipObjects = [managedObject objectForKey:relationshipName];
    NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
    for (NSManagedObject *relationshipObject in relationshipObjects) {
      [relationshipArray addObject:[self dataStructureForManagedObject:relationshipObject]];
    }
    [valuesDictionary setObject:relationshipArray forKey:relationshipName];
  }
  return [valuesDictionary autorelease];
}

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
  NSMutableArray *dataArray = [[NSMutableArray alloc] init];
  for (NSManagedObject *managedObject in managedObjects) {
    [dataArray addObject:[self dataStructureForManagedObject:managedObject]];
  }
  return [dataArray autorelease];
}

- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
  NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
  NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
  return jsonString;
}

- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc
{
  NSString *objectName = [structureDictionary objectForKey:@"ManagedObjectName"];
  NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
  [managedObject setValuesForKeysWithDictionary:structureDictionary];

  for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
    NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
    if (![description isToMany]) {
      NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
      NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
      [managedObject setObject:childObject forKey:relationshipName];
      continue;
    }
    NSMutableSet *relationshipSet = [managedObject mutableSetForKey:relationshipName];
    NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
    for (NSDictionary *childStructureDictionary in relationshipArray) {
      NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
      [relationshipSet addObject:childObject];
    }
  }
  return managedObject;
}

- (NSArray*)managedObjectsFromJSONStructure:(NSString*)json withManagedObjectContext:(NSManagedObjectContext*)moc
{
  NSError *error = nil;
  NSArray *structureArray = [[CJSONDeserializer deserializer] deserializeAsArray:json error:&error];
  NSAssert2(error == nil, @"Failed to deserialize\n%@\n%@", [error localizedDescription], json);
  NSMutableArray *objectArray = [[NSMutableArray alloc] init];
  for (NSDictionary *structureDictionary in structureArray) {
    [objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
  }
  return [objectArray autorelease];
}

ตอนนี้เป็นแบบวนซ้ำเพื่อให้คุณสามารถแปลร้านค้าถาวรทั้งหมดของคุณได้อย่างง่ายดายหากคุณไม่ระมัดระวัง ดูความสัมพันธ์ของคุณและตรวจสอบให้แน่ใจว่าพวกเขา "ลง" โครงสร้างวัตถุเพื่อให้คุณได้รับวัตถุที่คุณต้องการแปลเท่านั้น


ขอขอบคุณอีกครั้งสำหรับคำตอบที่ยอดเยี่ยมและหนังสือที่เป็นประโยชน์ของคุณ! :)
Urizen

2
สวัสดีมาร์คัส ฉันเพิ่งลองใช้รหัสด้านบน (มีการแก้ไขเล็กน้อยเพื่อให้คอมไพล์และการดำเนินการดูเหมือนจะดำเนินต่อไปเรื่อย ๆ จนกว่าแอปจะหยุดทำงาน) ขออภัยที่รบกวนคุณ แต่ฉันอยากรู้ว่าคุณอาจชี้ทางที่ถูกต้องในการแก้ปัญหานี้ให้ฉันได้หรือไม่ ดูเหมือนว่าจะเกิดขึ้นกับการเรียกซ้ำในเมธอด datastructureFromManagedObject ...
Urizen

1
ขึ้นอยู่กับโครงสร้างข้อมูลของคุณ ถ้าแบบจำลองของคุณสร้างลูปมันจะทำงานตลอดไป ตรวจสอบโมเดลข้อมูลของคุณและตรวจสอบให้แน่ใจว่าเป็นการออกแบบโครงสร้างแบบต้นไม้หรือใส่ตรรกะหยุดในโค้ดเรียกซ้ำเพื่อป้องกันการวนซ้ำ
Marcus S.Zarra

1
คุณได้ลองรันโค้ดนี้แล้วหรือยัง? มีข้อผิดพลาดมากมาย dataStructureForManagedObject ไม่มีอยู่จริง ฉันคิดว่ามันอาจจะเป็นการพิมพ์ผิด แต่ถ้าคุณเปลี่ยนเป็น dataStructureFromManagedObject สิ่งทั้งหมดก็วนกลับมาอย่างไม่สิ้นสุดระหว่างคู่ความสัมพันธ์ ฉันพลาดรหัสพิเศษที่นี่หรือไม่?
Chris Mitchelmore

1
ตัวอย่างโค้ดนี้เขียนในเบราว์เซอร์เมื่อสองปีก่อน มีวัตถุประสงค์เพื่อสร้างแรงบันดาลใจไม่ใช่การคัดลอกและวาง สำหรับการวนซ้ำแบบไม่มีที่สิ้นสุดนั่นหมายความว่าคุณมีการวนซ้ำในโมเดลของคุณจากนั้นคุณจะต้องเพิ่มตรรกะเฉพาะของโมเดลให้กับแอปของคุณเพื่อตัดวงจร มีหลายวิธีในการดำเนินการที่ไม่ได้อยู่ในตัวอย่างนี้
Marcus S.Zarra

12

ฉันแค่อยากจะชี้ให้เห็นการพิมพ์ผิดเล็ก ๆ น้อย ๆ ที่ทำให้โค้ดพังและหวังว่านี่จะช่วยให้คุณประหยัดเวลาได้ไม่กี่นาที

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {

    NSMutableArray *dataArray = [[NSArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return [dataArray autorelease];
}

NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray

ควรจะเป็นจริงๆ NSMutableArray *dataArray = [[NSMutableArray alloc] init];

นั้นคือทั้งหมด.

ขอขอบคุณ


10

การซิงโครไนซ์ข้อมูลหลักกับ Railsเป็นการนำเสนอโดยละเอียดที่มีโค้ดตัวอย่างสำหรับการทำให้เป็นอนุกรม / การแยกส่วนของออบเจ็กต์ข้อมูลหลักของคุณไปยัง / จาก JSON (ข้ามไปที่สไลด์ 55 สำหรับส่วนข้อมูลหลัก) โค้ดตัวอย่างของเขาถือว่าเป็นโมเดลที่ค่อนข้างเรียบง่ายโดยไม่มีความสัมพันธ์แม้ว่าฉันคิดว่ามันจะค่อนข้างง่ายที่จะขยาย

ยังนำเสนอไปในรายละเอียดบางอย่างเกี่ยวกับการรักษารูปแบบหลักของข้อมูลในซิงค์กับโปรแกรมเว็บ REST-based ด้วยการชี้ไปยังห้องสมุดที่มีประโยชน์บางอย่างรวมทั้งObjectiveResourceและASIHTTPRequest ไม่แน่ใจว่านั่นคือสิ่งที่คุณกำลังพยายามทำอยู่หรือไม่ แต่มันก็คุ้มค่าที่จะมองหารหัส Core Data


7

หากคุณมีNSDateออบเจ็กต์ที่มีการจัดการดังที่ได้กล่าวไว้ข้างต้นในความคิดเห็นข้อใดข้อหนึ่งคุณจะมีปัญหาในการจัดลำดับวัตถุที่มีไฟล์NSDate. วิธีแก้ไขง่ายๆคือเพิ่มJSONDataRepresentationวิธีการNSDateใช้ประเภทวัตถุประสงค์ -c

เพิ่มสองไฟล์นี้ในโครงการของคุณ:

NSdate.h:

#import <Foundation/Foundation.h>

@interface NSDate (jsondatarepresentation) 

- (NSData*) JSONDataRepresentation;

@end

NSDate.m:

#import "NSDate.h"

@implementation NSDate (jsondatarepresentation)

- (NSData*) JSONDataRepresentation {
    return [[[NSNumber numberWithDouble:[self timeIntervalSince1970]] stringValue] dataUsingEncoding:NSUTF8StringEncoding];
}

@end

3

แค่คิดว่า id โพสต์การอัปเดตอย่างรวดเร็วสำหรับคำถามนี้ ฉันทำตามคำตอบโดย Marcus และ Brandon และคิดสิ่งนี้สำหรับการส่งออก JSON (ใช้ TouchJSON ยังคงอยู่):

- (NSData*)jsonStructureFromManagedObjects:(NSArray*)managedObjects
{
    NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
    NSData *jsonData      = [[CJSONSerializer serializer] serializeArray:objectsArray error:nil];
    return jsonData;
}

- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects
{
    NSMutableArray *dataArray = [[NSMutableArray alloc] init];
    for (NSManagedObject *managedObject in managedObjects) {
        [dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
    }
    return dataArray;
}

- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject*)managedObject
{
    NSDictionary *attributesByName        = [[managedObject entity] attributesByName];
    NSDictionary *relationshipsByName     = [[managedObject entity] relationshipsByName];
    NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
    [valuesDictionary setObject:[[managedObject entity] name] forKey:@"ManagedObjectName"];

    for (NSString *relationshipName in [relationshipsByName allKeys]) {

        NSRelationshipDescription *description = [[[managedObject entity] relationshipsByName] objectForKey:relationshipName];

        if ([[[description userInfo] objectForKey:@"isExportable"] boolValue] == YES) {

            if (![description isToMany]) {
                NSManagedObject *relationshipObject = [managedObject valueForKey:relationshipName];
                if (relationshipObject) {
                    [valuesDictionary setObject:[self dataStructureFromManagedObject:relationshipObject] forKey:relationshipName];
                }

                continue;
            }

            NSSet *relationshipObjects        = [managedObject valueForKey:relationshipName];
            NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];

            for (NSManagedObject *relationshipObject in relationshipObjects) {
                [relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
            }

            [valuesDictionary setObject:relationshipArray forKey:relationshipName];

        }

    }
    return valuesDictionary;
}

ฉันไม่สามารถนำเข้าใช้งานได้บางทีอาจมีบางอย่างเกี่ยวข้องกับการที่ฉันใช้ Magical Record ฉันไม่แน่ใจดังนั้นฉันแค่วนลูปสตรีม JSON ที่เข้ามาและสร้างวัตถุด้วยตนเอง ...



2

ฉันเจอโพสต์นี้ซึ่งได้ผลดีมาก

http://touchalicious.com/blog/2009/10/25/turn-core-data-models-into-json.html

เนื่องจากสิ่งนี้เกิดขึ้นซ้ำแล้วซ้ำอีกความสัมพันธ์แบบกลุ่มต่อกลุ่มจะวนซ้ำไปซ้ำมา เพื่อหลีกเลี่ยงปัญหานี้ฉันได้เพิ่มคีย์ "isExportable" ลงในพจนานุกรมข้อมูลผู้ใช้ของความสัมพันธ์ในโมเดลข้อมูลหลักของฉัน จากนั้นคุณสามารถตรวจสอบคีย์นี้และเลือกที่จะไม่วนซ้ำความสัมพันธ์หากไม่มีคีย์นี้

ป้อนคำอธิบายภาพที่นี่

if ([property isKindOfClass:[NSRelationshipDescription class]])
    {
        NSRelationshipDescription *relationshipDescription = (NSRelationshipDescription *)property;

        if ([[[relationshipDescription userInfo] objectForKey:@"isExportable"] boolValue] == YES)
        {
            NSString *name = [relationshipDescription name];

            if ([relationshipDescription isToMany])
            {
                NSMutableArray *arr = [properties valueForKey:name];
                if (!arr)
                {
                    arr = [[NSMutableArray alloc] init];
                    [properties setValue:arr forKey:name];
                }

                for (NSManagedObject *o in [self mutableSetValueForKey:name])
                {
                    [arr addObject:[o propertiesDictionary]];
                }
            }
            else
            {
                NSManagedObject *o = [self valueForKey:name];
                [properties setValue:[o propertiesDictionary] forKey:name];
            }
        }
    }
}

1

Marcus S.Zarra เป็นแรงบันดาลใจให้ฉันนำแนวคิดแบบวนซ้ำไปสู่เวอร์ชันที่ใช้งานได้ ในเวอร์ชันนี้คุณไม่จำเป็นต้องตั้งค่าคีย์ใน CoreData และคุณสามารถตัดและวางลงในโปรเจ็กต์ของคุณได้ :-)

// MARK: - encoding and decoding CoreData entity to dictionary

func dataStructureFromManagedObject( managedObject:NSManagedObject?, parentEntity: NSEntityDescription? = nil) -> NSMutableDictionary {
    if (managedObject != nil) {
        var attributesByName: NSDictionary = managedObject!.entity.attributesByName
        var relationshipsByName: NSDictionary  = managedObject!.entity.relationshipsByName
        var valuesImmutableDictionary: NSDictionary = managedObject!.dictionaryWithValuesForKeys( attributesByName.allKeys)
        var valuesDictionary: NSMutableDictionary = valuesImmutableDictionary.mutableCopy() as NSMutableDictionary
        valuesDictionary.setObject( managedObject!.entity.name!, forKey: "ManagedObjectName")
        for relationshipNameObject in relationshipsByName.allKeys {
            var relationshipName: NSString = relationshipNameObject as  NSString
            var relationshipDescription: NSRelationshipDescription? = relationshipsByName.objectForKey( relationshipName) as? NSRelationshipDescription
            if !relationshipDescription!.toMany {
                // ono to one
                if parentEntity == nil || (relationshipDescription! as NSRelationshipDescription).destinationEntity != parentEntity! {
                    // no parent or relationship is "downward" -> object for relationship must be added
                    var relationshipObject: NSManagedObject? = managedObject!.valueForKey( relationshipName) as? NSManagedObject
                    var relationshipObjectDictionary: NSMutableDictionary = self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity)
                    valuesDictionary.setObject( relationshipObjectDictionary, forKey: relationshipName)
                } else {
                    // relationship is "upward" -> nothing to do
                }
            } else {
                // one to many -> all objects must be added
                var relationshipObjects: NSSet = managedObject!.mutableSetValueForKey( relationshipName)
                var relationshipArray:NSMutableArray = []
                for relationshipObjectRaw in relationshipObjects {
                    var relationshipObject:NSManagedObject? = relationshipObjectRaw as? NSManagedObject
                    if relationshipObject != nil && !relationshipObject!.entity.isKindOfEntity( managedObject!.entity) {
                        relationshipArray.addObject(self.dataStructureFromManagedObject( relationshipObject, parentEntity: managedObject?.entity))
                    }
                }
                valuesDictionary.setObject( relationshipArray, forKey: relationshipName)
            }
        }
        return valuesDictionary
    } else {
        return NSMutableDictionary()
    }
}

func managedObjectFromStructure( structureDictionary: NSDictionary, moc: NSManagedObjectContext, parentObject: NSManagedObject? = nil) -> NSManagedObject {
    if structureDictionary.count > 0 {
        var objectName:NSString = structureDictionary.objectForKey( "ManagedObjectName") as NSString
        var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
        var relationshipsByName: NSDictionary  = managedObject.entity.relationshipsByName
        var realObjectStructure:NSMutableDictionary = structureDictionary.mutableCopy() as NSMutableDictionary
        realObjectStructure.removeObjectForKey( "ManagedObjectName")
        for key in realObjectStructure.allKeys {
            // search for "ManagedObjectName" relationship entrys and delete them before filling the managedObject from this structure
            for relationshipName in relationshipsByName.allKeys {
                if relationshipName as NSString == key as NSString {
                    realObjectStructure.removeObjectForKey( key)
                }
            }
        }
        managedObject.setValuesForKeysWithDictionary( realObjectStructure)
        // the main object with attributes is created. Now care about the relationships
        for relationshipName in managedObject.entity.relationshipsByName.keys {
            var description:NSRelationshipDescription = relationshipsByName.objectForKey( relationshipName) as NSRelationshipDescription
            if !description.toMany {
                // to one relationship
                if parentObject == nil || description.destinationEntity != parentObject!.entity {
                    // no parent or relationship is "downward" -> recurse structure to add
                    var childStructureDictionary:NSDictionary = structureDictionary.objectForKey( relationshipName) as NSDictionary
                    if childStructureDictionary.count > 0 {
                        // dictionary not empty -> object must be created and added
                        var childObject:NSManagedObject? = self.managedObjectFromStructure( childStructureDictionary, moc: moc, parentObject: managedObject)
                        // validateForUpdate
                        var error:NSError?
                        if !managedObject.validateForUpdate( &error) {
                            println("Error: Object not in valid state for update!!! -> \(error)")
                        } else {
                            managedObject.setValue( childObject, forKey: relationshipName as NSString)
                        }
                    } else {
                        // relationship is "upward" -> nothing to do
                    }
                }
            } else {
                // to many relationship
                var relationshipSet:NSMutableSet = managedObject.mutableSetValueForKey( relationshipName as NSString)
                var relationshipArray:NSArray = structureDictionary.objectForKey( relationshipName as NSString) as NSArray
                for childStructureDictionary in relationshipArray {
                    if childStructureDictionary.count > 0 {
                        // dictionary not empty -> object must be created and added
                        var childObject:NSManagedObject = self.managedObjectFromStructure( childStructureDictionary as NSDictionary, moc: moc, parentObject: managedObject)
                        // validateForUpdate
                        var error:NSError?
                        if !managedObject.validateForUpdate( &error) {
                            println( "Error: Object not in valid state for update!!! -> \(error)")
                        } else {
                            relationshipSet.addObject( childObject)
                        }
                    } else {
                        // no object was behind the relationship -> nothing to do
                    }
                }
                // save set
                managedObject.setValue( relationshipSet, forKey: relationshipName as NSString)
            }
        }
        // final check validateForUpdate
        var error:NSError?
        if !managedObject.validateForUpdate( &error) {
            println( "Error: Object not in valid state for update although all previous check are passed!!! -> \(error)")
        }
        return managedObject
    } else {
        println( "Error: structure for object was empty. this should not happen at this point")
        var objectName:NSString = structureDictionary.objectForKey( "ManagedObjectName") as NSString
        var managedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName( objectName, inManagedObjectContext: moc) as NSManagedObject
        return managedObject
    }
}

func dataStructuresFromManagedObjects( managedObjects: NSArray) -> NSArray {
    var dataArray:NSMutableArray = []
    for managedObject in managedObjects {
        dataArray.addObject( self.dataStructureFromManagedObject(managedObject as? NSManagedObject))
    }
    return dataArray
}

กุญแจสำคัญในที่นี้คือการส่งผ่านเอนทิตีแม่เป็นอาร์กิวเมนต์ไปยังการเรียกซ้ำดังนั้นเราจึงสามารถตัดสินใจได้ว่าเราต้องเติมข้อมูลความสัมพันธ์ใด ดังนั้นฟังก์ชันทั้งสอง: dataStructureFromManagedObjectและmanagedObjectFromStructureสามารถเข้ารหัสและถอดรหัสเอนทิตีอ็อบเจ็กต์ใด ๆ จาก CoreData ลงในพจนานุกรมและกลับเข้าไปในอ็อบเจ็กต์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.