JSON.stringify () dziwaczne tablice z Prototype.js


89

Próbuję dowiedzieć się, co poszło nie tak z moją serializacją json, mam bieżącą wersję mojej aplikacji i starą i znajduję zaskakujące różnice w sposobie działania JSON.stringify () (Korzystanie z biblioteki JSON z json.org ).

W starej wersji mojej aplikacji:

 JSON.stringify({"a":[1,2]})

daje mi to;

"{\"a\":[1,2]}"

w nowej wersji,

 JSON.stringify({"a":[1,2]})

daje mi to;

"{\"a\":\"[1, 2]\"}"

masz jakiś pomysł, co mogło się zmienić, aby ta sama biblioteka umieszczała cudzysłowy wokół nawiasów tablicy w nowej wersji?


4
wygląda na to, że jest to konflikt z biblioteką Prototype, którą wprowadziliśmy w nowszej wersji. Jakieś pomysły, jak zdefiniować obiekt json zawierający tablicę w Prototype?
morgancodes

26
dlatego ludzie powinni powstrzymywać się od manipulowania globalnymi obiektami wbudowanymi (tak jak robi to prototypowy framework)
Gerardo Lima,

Odpowiedzi:


82

Ponieważ JSON.stringify jest ostatnio dostarczany z niektórymi przeglądarkami, sugerowałbym użycie go zamiast toJSON Prototype. Następnie należy sprawdzić window.JSON && window.JSON.stringify i dołączyć tylko bibliotekę json.org w innym przypadku (przez document.createElement('script')…). Aby rozwiązać niezgodności, użyj:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}

Nie ma potrzeby sprawdzania window.JSON we własnym kodzie - skrypt json.org robi to sam
zcrar70

Może tak być, ale wtedy cały plik skryptu musi zostać załadowany, nawet jeśli nie będzie potrzebny.
Raphael Schweikert

11
Właściwie jedyną instrukcją potrzebną do rozwiązania tego pytania jest: delete Array.prototype.toJSON
Jean Vincent.

1
Dziękuję bardzo. Firma, dla której obecnie pracuję, nadal używa prototypów w większości naszego kodu, co uratowało życie przy korzystaniu z bardziej nowoczesnych bibliotek, w przeciwnym razie wszystko się zepsuje.
krob,

1
Szukałem tej odpowiedzi przez DNI i opublikowałem dwa różne pytania SO, próbując to rozgryźć. Postrzegałem to jako pokrewne pytanie, gdy pisałem trzecie. Dziękuję bardzo!
Matthew Herbst

78

Funkcja JSON.stringify () zdefiniowana w ECMAScript 5 i nowszych (Strona 201 - The JSON Object, pseudokod Strona 205) , używa funkcji toJSON (), gdy jest dostępna na obiektach.

Ponieważ Prototype.js (lub inna używana biblioteka) definiuje funkcję Array.prototype.toJSON (), tablice są najpierw konwertowane na ciągi za pomocą Array.prototype.toJSON (), a następnie ciąg cytowany przez JSON.stringify (), stąd niepoprawne dodatkowe cudzysłowy wokół tablic.

Rozwiązanie jest zatem proste i trywialne (jest to uproszczona wersja odpowiedzi Raphaela Schweikerta):

delete Array.prototype.toJSON

Daje to oczywiście efekty uboczne w bibliotekach, które polegają na właściwości funkcji toJSON () dla tablic. Ale uważam to za drobną niedogodność, biorąc pod uwagę niekompatybilność z ECMAScript 5.

Należy zaznaczyć, że obiekt JSON zdefiniowany w ECMAScript 5 jest sprawnie implementowany w nowoczesnych przeglądarkach, dlatego najlepszym rozwiązaniem jest dostosowanie się do standardu i modyfikacja istniejących bibliotek.


5
To jest najbardziej zwięzła odpowiedź na to, co się dzieje z dodatkowym cytowaniem tablicy.
tmarthal

15

Możliwym rozwiązaniem, które nie wpłynie na inne zależności prototypu, byłoby:

var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
    var _array_tojson = Array.prototype.toJSON;
    delete Array.prototype.toJSON;
    var r=_json_stringify(value);
    Array.prototype.toJSON = _array_tojson;
    return r;
};

Zajmuje się to niekompatybilnością Array toJSON z JSON.stringify, a także zachowuje funkcjonalność toJSON, ponieważ inne biblioteki Prototype mogą od niego zależeć.


Użyłem tego fragmentu w witrynie internetowej. To powoduje problemy. Powoduje to, że właściwość toJSON tablicy jest niezdefiniowana. Jakieś wskazówki na ten temat?
Sourabh

1
Upewnij się, że Twój Array.prototype.toJSON jest zdefiniowany przed użyciem powyższego fragmentu kodu w celu ponownego zdefiniowania JSON.stringify. W moim teście działa dobrze.
akkishore

2
Zapakowałem się w if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Zadziałało.
Sourabh

1
Świetny. Problem występuje tylko do Prototype 1.7. Proszę o głosowanie :)
akkishore

1
Problem dotyczy wersji <1.7
Sourabh

9

Edytuj, aby uczynić trochę dokładniejszym:

Kod klucza problemu znajduje się w bibliotece JSON z JSON.org (i innych implementacjach obiektu JSON ECMAScript 5):

if (value && typeof value === 'object' &&
  typeof value.toJSON === 'function') {
  value = value.toJSON(key);
}

Problem polega na tym, że biblioteka Prototype rozszerza Array o metodę toJSON, którą obiekt JSON wywoła w powyższym kodzie. Gdy obiekt JSON osiąga wartość tablicy, wywołuje toJSON na tablicy zdefiniowanej w Prototype, a ta metoda zwraca wersję ciągu tablicy. Stąd cudzysłowy wokół nawiasów tablicy.

Jeśli usuniesz toJSON z obiektu Array, biblioteka JSON powinna działać poprawnie. Lub po prostu użyj biblioteki JSON.


2
To nie jest błąd w bibliotece, ponieważ jest to dokładny sposób definiowania JSON.stringify () w ECMAScript 5. Problem dotyczy prototype.js, a rozwiązaniem jest: usuń Array.prototype.toJSON To będzie miało jakąś stronę efekty dla serializacji prototypu do JSON, ale znalazłem te drobne w związku z niekompatybilnością prototypu z ECMAScript 5.
Jean Vincent

biblioteka Prototype nie rozszerza Object.prototype, ale Array.prototype, chociaż tablica typeof w JavaScript również zwraca „obiekt”, nie mają one tego samego „konstruktora” i prototypu. Aby rozwiązać problem, musisz: "usunąć Array.prototype.toJSON;"
Jean Vincent,

@Jean Aby być uczciwym, Prototype rozszerza wszystkie podstawowe obiekty natywne, w tym Object. Ale ok, znowu rozumiem twój punkt widzenia :) Dziękuję za pomoc w ulepszeniu mojej odpowiedzi
Bob

Prototype od dłuższego czasu przestał rozszerzać "Object.prototype" (nie pamiętam jednak, która wersja), aby uniknąć problemów z for ... in. Teraz rozszerza tylko statyczne właściwości Object (co jest znacznie bezpieczniejsze) jako przestrzeń nazw: api.prototypejs.org/language/Object
Jean Vincent.

Jean, właściwie to jest dokładnie błąd w bibliotece. Jeśli obiekt ma toJSON, należy go wywołać i użyć jego wyniku, ale nie powinien być cytowany.
grr,

4

Myślę, że lepszym rozwiązaniem byłoby włączenie tego zaraz po załadowaniu prototypu

JSON = JSON || {};

JSON.stringify = function(value) { return value.toJSON(); };

JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };

To sprawia, że ​​funkcja prototypu jest dostępna jako standardowe JSON.stringify () i JSON.parse (), ale zachowuje natywną JSON.parse (), jeśli jest dostępna, dzięki czemu jest bardziej kompatybilna ze starszymi przeglądarkami.


wersja JSON.stringify nie działa, jeśli przekazana „wartość” jest obiektem. Zamiast tego powinieneś to zrobić: JSON.stringify = function (wartość) {return Object.toJSON (wartość); };
akkishore

2

Nie jestem biegły w Prototype, ale widziałem to w jego dokumentacji :

Object.toJSON({"a":[1,2]})

Nie jestem jednak pewien, czy miałoby to ten sam problem, co obecne kodowanie.

Istnieje również dłuższy samouczek dotyczący używania JSON z Prototype.


2

Oto kod, którego użyłem do tego samego problemu:

function stringify(object){
      var Prototype = window.Prototype
      if (Prototype && Prototype.Version < '1.7' &&
          Array.prototype.toJSON && Object.toJSON){
              return Object.toJSON(object)
      }
      return JSON.stringify(object)
}

Sprawdzasz, czy Prototyp istnieje, a następnie sprawdzasz wersję. Jeśli stara wersja, użyj Object.toJSON (jeśli jest zdefiniowana), we wszystkich innych przypadkach powrót do JSON.stringify ()


1

Oto jak sobie z tym radzę.

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);

1

Moje tolerancyjne rozwiązanie sprawdza, czy Array.prototype.toJSON jest szkodliwe dla stringify JSON i zachowuje je, gdy to możliwe, aby otaczający kod działał zgodnie z oczekiwaniami:

var dummy = { data: [{hello: 'world'}] }, test = {};

if(Array.prototype.toJSON) {
    try {
        test = JSON.parse(JSON.stringify(dummy));
        if(!test || dummy.data !== test.data) {
            delete Array.prototype.toJSON;
        }
    } catch(e) {
        // there only hope
    }
}

1

Jak ludzie zauważyli, jest to spowodowane Prototype.js - konkretnie wersjami wcześniejszymi niż 1.7. Miałem podobną sytuację, ale musiałem mieć kod, który działał niezależnie od tego, czy Prototype.js był obecny, czy nie; oznacza to, że nie mogę po prostu usunąć Array.prototype.toJSON, ponieważ nie jestem pewien, na czym on polega. W tej sytuacji jest to najlepsze rozwiązanie, które wymyśliłem:

function safeToJSON(item){ 
    if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
        return JSON.stringify(item); //sane behavior
    } else { 
        return item.toJSON(); // Prototype.js nonsense
    }
}

Mam nadzieję, że to komuś pomoże.


0

Jeśli nie chcesz zabijać wszystkiego i masz kod, który byłby w porządku w większości przeglądarek, możesz to zrobić w ten sposób:

(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
  if (true ||typeof (Prototype) !== 'undefined') {
    // First, ensure we can access the prototype of an object.
    // See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
    if(typeof (Object.getPrototypeOf) === 'undefined') {
      if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          return object.__proto__;
        };
      } else {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          // May break if the constructor has been changed or removed
          return object.constructor ? object.constructor.prototype : undefined;
        }
      }
    }

    var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
    JSON.stringify = function stringify (obj) {
      var obj_prototype = Object.getPrototypeOf(obj),
          old_json = obj_prototype.toJSON, // We save the toJSON of the object
          res = null;
      if (old_json) { // If toJSON exists on the object
        obj_prototype.toJSON = undefined;
      }
      res = _json_stringify.apply(this, arguments);
      if (old_json)
        obj_prototype.toJSON = old_json;
      return res;
    };
  }
}.call(this));

Wydaje się to skomplikowane, ale jest skomplikowane tylko w przypadku większości przypadków użycia. Główną ideą jest nadpisanie JSON.stringifyusunięcia toJSONz obiektu przekazanego jako argument, następnie wywołanie starego JSON.stringifyi na koniec przywrócenie go.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.